// The class that handles the main data extraction window, plus a
// few helper classes
//
// Copyright (c) 2000, 2004 Markus Demleitner
//  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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
// tabsize=2

import java.util.*;

import java.awt.event.*;
import java.awt.Color;
import java.awt.Container;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.Image;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.InputEvent;

import javax.swing.event.*;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;

// The text fields (and the log box) to specify beginning and and
// of one gauge -- may be either horizontal or vertical.
class GaugeGauge extends JPanel
	implements DocumentListener
{	
	JTextField start, end;
	int naturalx=0, naturaly=0;
	boolean vertical;
	GridBagLayout layout = new GridBagLayout();
	JCheckBox islog;
	DExtractor parent;
	static final long serialVersionUID=20060308L;

	private class NumOnly extends KeyAdapter
	{	
		public void keyPressed(KeyEvent e)
		{	
			char c=e.getKeyChar();
		
			if (c>=' ')
				if (!((c>='0'&&c<='9')||c=='-'||c=='+'||c=='e'||c=='.'||c=='E'))
					e.consume();
		}
	}

	GaugeGauge(String variable, Color bgcolor, boolean vertical,
		DExtractor parent)
	{	
		GridBagConstraints c = new GridBagConstraints();
		JLabel lab;
		String labels[] = {"0", "1"};
		int labelalign;
		JTextField nf;

		this.vertical = vertical;
		this.parent = parent;
		setForeground(bgcolor);
		this.setLayout(layout);
		start = new JTextField("", 8);
		end = new JTextField("", 8);
		islog = new JCheckBox("log", false);

		if (vertical) {	
			String tmp;
			tmp = labels[0];
			labels[0] = labels[1];
			labels[1] = tmp;
			c.fill = GridBagConstraints.BOTH;
			c.gridwidth = GridBagConstraints.REMAINDER;
			labelalign = JLabel.LEFT;
		}
		else {	
			c.fill = GridBagConstraints.HORIZONTAL;
			labelalign = JLabel.RIGHT;
		}	
		c.weighty = 0.5;
		c.weightx = 0;
		lab = new JLabel(variable+labels[0], labelalign);
		layout.setConstraints(lab, c);
		this.add(lab);

		c.weightx = 0.5;
		c.weighty = 0.5;
		if (vertical)
			nf = end;
		else
			nf = start;
		layout.setConstraints(nf, c);
		this.add(nf);
		nf.addKeyListener(new NumOnly());
		nf.getDocument().addDocumentListener(this);

		int oldfill = c.fill;
		c.fill = GridBagConstraints.NONE;
		layout.setConstraints(islog, c);
		this.add(islog);
		c.fill = oldfill;

		lab = new JLabel(variable+labels[1], labelalign);
		c.weightx = 0;
		c.weighty = 0.5;
		layout.setConstraints(lab, c);
		this.add(lab);

		c.weightx = 0.5;
		c.weighty = 0.5;
		if (vertical)
			nf = start;
		else
			nf = end;
		layout.setConstraints(nf, c);
		this.add(nf);
		nf.addKeyListener(new NumOnly());
		nf.getDocument().addDocumentListener(this);
	}

	public double getstart()
	{
		return Double.valueOf(this.start.getText()).doubleValue();
	}

	public double getend()
	{
		return Double.valueOf(this.end.getText()).doubleValue();
	}

	public int computeUsefulSignificantDigits()
	{
		try {
			return Math.max(4, 
				(int)Math.round(-Math.log(
					Math.abs(this.getend()-this.getstart())/this.getstart())/2.3
				+4));
		} catch (NumberFormatException e) {
			return 4;
		}
	}

	public boolean isLogAxis()
	{	
		return islog.isSelected();
	}

	public Dimension getPreferredSize()
	{
		return layout.preferredLayoutSize((Container)this);
	}

	public Dimension getMinimumSize()
	{	
		return getPreferredSize();
	}

	private void textValueChanged()
	{	
		try {
			this.parent.computeTransform();
		} catch (CantComputeException e) {
		}
	}

	public void insertUpdate(DocumentEvent e) {
		this.textValueChanged();
	}

	public void removeUpdate(DocumentEvent e) {
		this.textValueChanged();
	}

	public void changedUpdate(DocumentEvent e) {
		this.textValueChanged();
	}
}


// This handles the window in which most of the work gets done.
// Clearly, this should be split into at least three subclasses.
public class DExtractor extends JFrame
	implements ActionListener, ComponentListener
{ 

	class Steeringstuff extends JPanel
	{	
		int numbut;
		JButton buttons[];
		static final long serialVersionUID=20060308L;


		// a container for some buttons, mainly here because I believe I am
		// too dumb for the awt layout managers
		Steeringstuff(JButton buttons_p[], int numbut_p, ActionListener l)
		{	
			int i;

			numbut = numbut_p;
			buttons = buttons_p;
			this.setLayout(new GridLayout(numbut, 1));
			for (i=0; i<numbut; i++) {	
				this.add(buttons[i]);
				buttons[i].addActionListener(l);
			}
		}

	}


	ImageWithPoints imzone;
	ImageGetter imageGetter;
	DataDeliverer dataDeliverer;
	GaugeGauge hgg, vgg;
	MainServices myparent;

	Color hcolor, vcolor;
	boolean recogniserRunning = false;
	boolean statusLocked = false;
	Vector<Component> criticalComponents = new Vector<Component>();
	Vector<JMenuItem> criticalMenuItems = new Vector<JMenuItem>();

	JTextArea results;
	Steeringstuff apanel;
	MagGlass magGlass;
	JScrollPane imageScroller;
	JScrollPane textScroller;
	JPanel helppanel = new JPanel();
	JLabel rulerLabel;
	JTextField fnamefield;
	JMenuItem stopRecogniserItem;
	JCheckBox sortX;
	RecogniserSettings recSettings=new RecogniserSettings();

	Rectangle bbox;
	String datasetName;
	String targetFName;
	int scale=3;
	static final long serialVersionUID = 20060308L;
	AffineTrafo transformation = null;

	
	// TODO: give DExtractor a seperate config class.
	// sourcebib, sourcepage, xAxisColor and yAxisColor should be
	// in there, I guess.  Possibly quite a few more...
	DExtractor(MainServices parent, Rectangle bbox, String sourcebib,
		String sourcepage, ImageGetter imageGetter,
		DataDeliverer dataDeliverer, Color xAxisColor, Color yAxisColor,
		int defaultScale) 
	{
		this.datasetName = "# Graph from "+sourcebib+", page "+sourcepage;
		this.targetFName = sourcebib;
		this.imageGetter = imageGetter;
		this.myparent = parent;
		this.bbox = bbox;
		this.dataDeliverer = dataDeliverer;
		this.hcolor = xAxisColor;
		this.vcolor = yAxisColor;
		this.scale = defaultScale;

		this.addComponentListener(this);
		this.addWindowListener(new WindowAdapter() { 
			public void windowClosing(WindowEvent event) 
			{ 
				closeWin();
      }
    });

		makeDialog();
		makeMenu();

		// delay this to we're ready to show out-of-memory errors and such
		Runnable loadImage = new Runnable() {
	    public void run() { 
				imzone.setImage(imageGetter.getImage(scale, bbox));
		  }
		};
		SwingUtilities.invokeLater(loadImage);

	}

	DExtractor(MainServices parent, Rectangle bbox, String sourcebib,
		String sourcepage, ImageGetter imageGetter,
		DataDeliverer dataDeliverer, Color xAxisColor, Color yAxisColor)
	{
		this(parent, bbox, sourcebib, sourcepage, imageGetter,
			dataDeliverer, xAxisColor, yAxisColor, 3);
	}

	DExtractor(MainServices parent, Rectangle bbox, String sourcebib,
		String sourcepage, ImageGetter imageGetter,
		DataDeliverer dataDeliverer)
	{
		this(parent, bbox, sourcebib, sourcepage, imageGetter,
			dataDeliverer, new Color(255, 0, 0), new Color(0, 0, 255), 3);
	}

	protected void makeMenu()
	{	
		JMenuBar mb;
		JMenuItem m;

		mb = new JMenuBar();
		this.setJMenuBar(mb);

		JMenu fileMenu = new JMenu("File");
		mb.add(fileMenu);
		m = new JMenuItem("Show Data");
		fileMenu.add(m);
		this.criticalMenuItems.addElement(m);
		m.addActionListener(this);
		m = new JMenuItem("Send Data");
		fileMenu.add(m);
		this.criticalMenuItems.addElement(m);
		m.addActionListener(this);
		m = new JMenuItem("Save Data");
		fileMenu.add(m);
		this.criticalMenuItems.addElement(m);
		fileMenu.addSeparator();
		m.addActionListener(this);
		m = new JMenuItem("Close");
		m.setAccelerator(
			KeyStroke.getKeyStroke("control W"));
		m.setActionCommand("Close");
		m.addActionListener(this);
		fileMenu.add(m);

		JMenu zoomMenu = new JMenu("Zoom");
		mb.add(zoomMenu);
		m = new JMenuItem("600 dpi");
		zoomMenu.add(m);
		this.criticalMenuItems.addElement(m);
		m.addActionListener(this);
		m = new JMenuItem("300 dpi");
		zoomMenu.add(m);
		this.criticalMenuItems.addElement(m);
		m.addActionListener(this);
		m = new JMenuItem("200 dpi");
		zoomMenu.add(m);
		this.criticalMenuItems.addElement(m);
		m.addActionListener(this);
		m = new JMenuItem("100 dpi");
		zoomMenu.add(m);
		this.criticalMenuItems.addElement(m);
		m.addActionListener(this);
		m = new JMenuItem("75 dpi");
		zoomMenu.add(m);
		this.criticalMenuItems.addElement(m);
		m.addActionListener(this);

		JMenu recMenu = new JMenu("Recognize");
		mb.add(recMenu);
		m = new JMenuItem("Trace a Line");
		m.setAccelerator(
			KeyStroke.getKeyStroke("control T"));
		m.setActionCommand("Trace a Line");
		recMenu.add(m);
		this.criticalMenuItems.addElement(m);
		m.addActionListener(this);
		m = new JMenuItem("Find Points");
		m.setAccelerator(
			KeyStroke.getKeyStroke("control F"));
		m.setActionCommand("Find Points");
		recMenu.add(m);
		this.criticalMenuItems.addElement(m);
		m.addActionListener(this);
		m = new JMenuItem("Automatic Axes");
		m.setAccelerator(
			KeyStroke.getKeyStroke("control A"));
		m.setActionCommand("Automatic Axes");
		recMenu.add(m);
		m.addActionListener(this);

		recMenu.addSeparator();
		m = new JMenuItem("Recognizer Settings");
		m.setAccelerator(
			KeyStroke.getKeyStroke("control S"));
		m.setActionCommand("Recognizer Settings");
		m.addActionListener(this);
		recMenu.add(m);

		stopRecogniserItem = new JMenuItem("Stop Recognizer");
		recMenu.add(stopRecogniserItem);
		stopRecogniserItem.setEnabled(false);
		stopRecogniserItem.addActionListener(this);
		recMenu.addSeparator();
		m = new JMenuItem("Delete all Points");
		m.addActionListener(this);
		recMenu.add(m);

		JMenu helpMenu = new JMenu("Help");
		mb.add(helpMenu);
		m = new JMenuItem("About Dexter");
		helpMenu.add(m);
		m.addActionListener(this);
		m = new JMenuItem("Help");
		helpMenu.add(m);
		m.addActionListener(this);
	}
		
	protected void makeDialog()
	{	
		GridBagLayout layout = new GridBagLayout();
		GridBagConstraints c = new GridBagConstraints();
		JButton actbuts[] = {	
			new JButton("Help"),
			new JButton("Show Data"),
			new JButton("Send Data"),
			new JButton("Save Data"),
			new JButton("Close"),
		};
		this.criticalComponents.addElement(actbuts[1]);
		this.criticalComponents.addElement(actbuts[2]);
		this.criticalComponents.addElement(actbuts[3]);

		JLabel lab;
		JPanel zwp;

		setFont(new Font("Helvetica", Font.PLAIN, 10));

		apanel = new Steeringstuff(actbuts, 5, this);
		fnamefield = new JTextField(targetFName);

		this.setLayout(layout);
		this.magGlass = new MagGlass();
		this.hgg = new GaugeGauge("x", this.hcolor, false, this);
		this.vgg = new GaugeGauge("y", this.vcolor, true, this);
		this.results = new JTextArea(
			"(Use 'Show Data' to get the extraction here)");
		this.results.setEditable(false);
		this.rulerLabel = new JLabel("        ");
		this.imzone = new ImageWithPoints(this, magGlass, recSettings);
		
		helppanel.setLayout(new GridBagLayout());
		c.fill = GridBagConstraints.BOTH;
		c.gridy = 0;
		((GridBagLayout)helppanel.getLayout()).setConstraints(apanel,c);
		helppanel.add(apanel);
		c.gridy = 1;
		((GridBagLayout)helppanel.getLayout()).setConstraints(vgg,c);
		helppanel.add(vgg);
		c.gridy = 2;
		((GridBagLayout)helppanel.getLayout()).setConstraints(magGlass,c);
		helppanel.add(magGlass);

		c.gridheight = 3;
		c.gridwidth = 1;
		c.gridx = 0;
		c.gridy = 0;
		c.fill = GridBagConstraints.NONE;
		c.weightx = 0;
		c.weighty = 1;
		c.anchor = GridBagConstraints.NORTH;
		layout.setConstraints(helppanel, c);
		this.add(helppanel);

		c.anchor = GridBagConstraints.CENTER;
		c.fill = GridBagConstraints.BOTH;
		c.gridwidth=GridBagConstraints.REMAINDER;
		c.gridheight=1;
		c.weightx = 1;
		c.weighty = 1;
		c.gridx = 1;
		c.gridy = 0;
		this.imageScroller = new JScrollPane(
			this.imzone,
			JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
			JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
		layout.setConstraints(this.imageScroller, c);
		this.add(this.imageScroller);

		c.fill = GridBagConstraints.NONE;
		c.gridwidth = 1;
		c.weightx = 0;
		c.weighty = 0;
		c.gridx = 1;
		c.gridy = 1;
		layout.setConstraints(hgg, c);
		this.add(hgg);

		sortX = new JCheckBox("Sorted", true);
		c.ipadx = 10;
		c.gridx = 2;
		layout.setConstraints(sortX, c);
		this.add(sortX);

		c.gridx = 3;
		lab = new JLabel("File name:");
		layout.setConstraints(lab, c);
		this.add(lab);

		c.gridx = 4;
		c.weightx = 1;
		c.fill = GridBagConstraints.HORIZONTAL;
		layout.setConstraints(fnamefield, c);
		this.add(fnamefield);

		this.textScroller = new JScrollPane(
			this.results,
			JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
			JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
		c.fill = GridBagConstraints.BOTH;
		c.gridx = 1;
		c.gridwidth=GridBagConstraints.REMAINDER;
		c.gridy = 2;
		c.weighty = 0.2;
		layout.setConstraints(this.textScroller, c);
		this.add(this.textScroller);

		c.fill = GridBagConstraints.HORIZONTAL;
		c.gridx = 1;
		c.gridwidth=GridBagConstraints.REMAINDER;
		c.gridy = 3;
		c.gridx = 0;
		c.weighty = 0;
		rulerLabel.setBackground(Color.white);
		layout.setConstraints(rulerLabel, c);
		this.add(rulerLabel);
	}

	// the layout manager kept giving me rubbish here, so I fumble together
	// something that might be remotely sensible
	public Dimension getPreferredSize()
	{ 
		return new Dimension(
			Math.max(this.magGlass.getPreferredSize().width,
				this.apanel.getPreferredSize().width)+
				this.imzone.getPreferredSize().width+20,
			Math.max(this.helppanel.getPreferredSize().height+20,
				this.imzone.getPreferredSize().height+40)+
				this.hgg.getPreferredSize().height+
				200+ // for the JScrollPane with the results
				this.rulerLabel.getPreferredSize().height);
	}

	public Dimension getMinimumSize()
	{	
		return new Dimension(600,400);
	}

	public void resizeToPreferredSize()
	{	
		Dimension pS=getPreferredSize();
		Dimension screenS=java.awt.Toolkit.getDefaultToolkit().getScreenSize();

		this.setSize(new Dimension(
			Math.min(screenS.width-40, Math.max(600, pS.width)),
			Math.min(screenS.height-40, Math.max(400, pS.height))));
		this.imageScroller.doLayout();
		validate();
		repaint();
	}

	private void newimagescale(int newscale) {	
		Image im;

		imzone.newScale(scale, newscale);
		im = imageGetter.getImage(newscale, bbox);
		imzone.setImage(im);
		magGlass.setImage(im);
		scale = newscale;
		this.resizeToPreferredSize();
	}
	
	public void closeWin()
	{
		setVisible(false);
		if (recSettings.isShowing()) {
			recSettings.close();
		}
		myparent.childClosed();
	}

	public void actionPerformed(ActionEvent e) {	
		if (e.getActionCommand()=="Close") {
			this.closeWin();
		}
		if (e.getActionCommand()=="75 dpi") {	
			newimagescale(8); 
		}
		if (e.getActionCommand()=="100 dpi") { 
			newimagescale(6); 
		}
		if (e.getActionCommand()=="200 dpi") {	
			newimagescale(3); 
		}
		if (e.getActionCommand()=="300 dpi") {
			newimagescale(2); 
		}
		if (e.getActionCommand()=="600 dpi") {	
			newimagescale(1); 
		}

		if ((e.getActionCommand()=="Show Data") ||
			(e.getActionCommand()=="Send Data") ||
			(e.getActionCommand()=="Save Data")) {	
			try { 
				makedata();
			} catch (CantComputeException exc) {	
				return; 
			}

			if (e.getActionCommand()=="Save Data") {
				dataDeliverer.deliver(results.getText(), fnamefield.getText(), 1);
			}
			if (e.getActionCommand()=="Send Data") {
				dataDeliverer.deliver(results.getText(), fnamefield.getText(), 0);
			}
		}

		if (e.getActionCommand()=="Help")
			myparent.showHelp();

		if (e.getActionCommand()=="About Dexter") {	
			JOptionPane.showMessageDialog(this, 
				"Dexter -- Data extraction applet"+
				"\nRelease 0.5a"+
				"\nA part of the NASA Astrophysics Data System\n"+
				"http://adswww.harvard.edu\n",
				"About Dexter",
				JOptionPane.INFORMATION_MESSAGE);
		}

		if (e.getActionCommand()=="Trace a Line") {
			this.startRecogniser("LineTracer");
		}
		if (e.getActionCommand()=="Find Points") {
			this.startRecogniser("PointFinder");
		}
		if (e.getActionCommand()=="Automatic Axes") {
			this.startRecogniser("AxisFinder");
		}

		if (e.getActionCommand()=="Recognizer Settings") {
			this.recSettings.open();
		}
		if (e.getActionCommand()=="Stop Recognizer") {
			this.stopRecogniser();
		}

		if (e.getActionCommand()=="Delete all Points") {
			this.imzone.delAllPoints();
		}
	}
	
	protected void enableCritical(boolean enable)
	{	
		for (Iterator<Component> it=this.criticalComponents.iterator();
				it.hasNext();) {
			it.next().setEnabled(enable);
		}
		for (Iterator<JMenuItem> it=this.criticalMenuItems.iterator();
				it.hasNext();) {
			it.next().setEnabled(enable);
		}
	}

	protected synchronized void startRecogniser(String recName)
	{
		try {
			this.imzone.startRecogniser(recName);
		} catch (Exception ex) {
			JOptionPane.showMessageDialog(
				this, ex.getMessage(), 
				"Dexter Error Message",
				JOptionPane.ERROR_MESSAGE);
			return;
		}
		this.recogniserRunning = true;
		this.enableCritical(false);
		this.stopRecogniserItem.setEnabled(true);
	}


	synchronized void recogniserStopped()
	{	
		this.recogniserRunning = false;
		this.releaseStatusLine();
		this.stopRecogniserItem.setEnabled(false);
		this.enableCritical(true);
	}


	synchronized void stopRecogniser()
	{	
			imzone.stopRecogniser();
	}


	// Compute the transformation -- zerox and zeroy are the zero points
	// of the axes in pixel coordinates, xaxis and yaxis are unit vectors
	// of these axes, islog[i] is true when the respective axis is 
	// logarithmic
	protected void computeTransform() throws CantComputeException
	{	
		try {	
			this.transformation = new AffineTrafo(
				imzone.hgauge.start, imzone.vgauge.start,
				imzone.hgauge.end, imzone.vgauge.end,
				this.hgg.getstart(), this.hgg.getend(),
				this.vgg.getstart(), this.vgg.getend(),
				this.hgg.isLogAxis(), this.vgg.isLogAxis());
		} catch (java.lang.NumberFormatException e) {	
			this.transformation = null;
			throw new CantComputeException("You must fill out both"+
				" horizontal and vertical\n gauge number fields with sensible values");
		} catch (NullPointerException e) {
			this.transformation = null;
			throw new CantComputeException("Both axes must be defined"+
				" before value\nreadout is possible.\n  Click and drag to"+
				" define axes.");
		} catch (MissingData e) {
			this.transformation = null;
			throw new CantComputeException(e.getMessage());
		}
	}


	private void makedata_orth() 
	{	
		Datapoint points[];
		boolean needVertBars=false, needHorizBars=false;
	
		// Update transform and bail out if we don't have enough
		// data to transform
		try {
			this.computeTransform();
		} catch (CantComputeException e) {
			 JOptionPane.showMessageDialog(
			 	this, e.getMessage(), "Dexter Error",
			 	JOptionPane.ERROR_MESSAGE);
			 return;
		}

		points = this.imzone.getPoints();
		// sort for abscissa value
		if (this.sortX.isSelected()) {
			QuickSort.getInstance().sort(
				points, 0, points.length-1, points[0]);
		}

		// check if we need to give error bars
		for (int i=0; i<points.length; i++) {
			Datapoint pt = points[i];
			needHorizBars |= pt.hasHorizErrBars();
			needVertBars |= pt.hasVertErrBars();
		}

		results.setText(datasetName+"\n");
		int sigFigX = this.hgg.computeUsefulSignificantDigits();
		int sigFigY = this.vgg.computeUsefulSignificantDigits();
		for (int i=0; i<points.length; i++) {
			results.append(points[i].getRepr(
				needHorizBars, needVertBars, 
				sigFigX, sigFigY));
		}
	}

	public void makedata() {	
		makedata_orth();
	}

	/**
	 * returns logical coordinates for the screen coordinates coo if a
	 * logical coordinate system is already defined, a DoublePoint for coo
	 * otherwise.
	 */
	public DoublePoint transformPhysicalToLogical(Point coo)
	{
		DoublePoint pt = new DoublePoint(coo);
		if (this.transformation!=null) {
			pt = this.transformation.transformPhysicalToLogical(coo);
		}
		return pt;
	}

	public void displayMousePos(Point mousePos)
	{	
		if (statusLocked) {
			return;
		}
		DoublePoint pt = this.transformPhysicalToLogical(mousePos);
		String posTx = Fmt.fmt(pt.getX(), 4, 
				this.hgg.computeUsefulSignificantDigits())+" / "+
				Fmt.fmt(pt.getY(), 4, this.vgg.computeUsefulSignificantDigits());
		if (recogniserRunning) {
			posTx = posTx+" Recog. running";
		}
		rulerLabel.setText(posTx);
	}

	public boolean allocStatusLine(String msg)
	{	
		if (statusLocked) {
			return false;
		}
		statusLocked = true;
		rulerLabel.setText(msg);
		return true;
	}


	public void releaseStatusLine()
	{
		statusLocked = false;
		rulerLabel.setText("");
	}

	public void componentResized(ComponentEvent e) {
	}

	public void componentHidden(ComponentEvent e) {}
	public void componentMoved(ComponentEvent e) {}
	public void componentShown(ComponentEvent e) {}
}
// vi:ts=2:
