Page 1 of 1

Slide Rule Back from the 70's Rate Topic: -----

#1 pbl  Icon User is offline

  • There is nothing you can't do with a JTable
  • member icon

Reputation: 8324
  • View blog
  • Posts: 31,857
  • Joined: 06-March 08

Posted 14 December 2010 - 08:59 PM

Many of you have probably never seen one, and even more never used one but here a cute (I think) Slide Rule calculator.

I used to work with one at secondary school but it is using them that the NASA engineers brought man to the moon.
A good user can perform calculations with up to 3 digits of precision, the average user 2 and newbies probably one.
The principle is based on log10 where you can apply any f(x) function.
If you draw log10 numbers on 2 scales and you slide the 2 scales one over the other you can easily perform a multiplication.

You will see by the image when you will run the GUI.
Plus the two regular slidinng "rules" I have added, for ma job, the number of pixels per centimeter
for 640X480, 800X600 and 1024X768 resolutions on our screens at the office. I was fed up to recalculate these values
20 times a day now I have them displayed in my screen.

In the center there are the 2 standard rules. The fixed one and the sliding one.
You can slide the top one (in ligh green) by holding the mouse down ove it and by dragging the mouse left or right.
Just slide the sliding rule to the right until it's "1" is over the "2" of the fixed rule.
You will see that under the "2" of the sliding rule is "4", under the "3" is "6"
thus: 2 * 2 et 2 * 3. Using the little ticks you may even see that under 2.5 is 5.0

There is also a cursor. The cursor has a thin green vertical line and join to yellow boxes (actually JLabel)
The top box contains the value the thin green line is over the sliding rule.
The lower box contains the value the thin green line passes ove the fixed rule.

Under these the 3 scales for the pixel per centimeter for the 3 resolutions.

To move the cursor just drag it to the left or the right holding the mouse down at the Y of one of the 2 boxes displaying the values.
If you move the cursor over the "3" on the fixed rule, you will see on the 800X600 scale the number of pixels for 3 centimeter is 78.
This 78 is also displayed in a JLabel at the complete right of the 800X600 scale.

Near the labels of the cursor are a + and a - JButtons. These are to move left or right one (1) single pixel the cursor.
At the complete bottom of the JFrame are 2 JSlider that determine the number of pixels that will be displayed on the left and the right of the slide rule.
This is just a gadget to let you see more or less of the sliding rule.

I put it in a Tutorial, not in CodeSnippet, because there are a few lines of code and because Snippet cannot be edited to add features of fix bugs.
Have fun. There are 5 classes.

SlideRuleFrame.java
A JFrame that contains the SlideRulePanel in CENTER and the MarginPanel SOUTH.
Contains also the main() method to run the application

import javax.swing.*;
import java.awt.*;


public class SlideRuleFrame extends JFrame {

	private static final long serialVersionUID = 1L;

	SlideRuleFrame(int width, int border) {
		super("SlideRule");
		SlideRuleMath srm = new SlideRuleMath();
		SlideRulePanel srp = new SlideRulePanel(srm);
		add(srp, BorderLayout.CENTER);
		add(new MarginPanel(srp), BorderLayout.SOUTH);
	}
	
	public static void main(String[] args) {
		// prepare the GUI to run in another thread
		Runnable detach = new Runnable() {
			public void run() {
				int width = 1000;
				int border = 20;
				SlideRuleFrame srf = new SlideRuleFrame(width, border);
				srf.setSize(width + 2 * border, 500);
				srf.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
				srf.setVisible(true);
			}
		};
		// start the thread
		SwingUtilities.invokeLater(detach);
		
	}
}


MarginPanel.java
This class defines the 2 JSlider put in the SOUTH area of the JFrame. It simply contains the
2 JSlider (with a JLable between) them and it calls back the SlideRulePanel when a value changes in the JSlider.

import javax.swing.*;

import java.awt.*;
import javax.swing.event.*;
import java.util.*;
/*
 * Display the Slider to select the left and right margin of the slide rule
 */
public class MarginPanel extends JPanel implements ChangeListener {

	private static final long serialVersionUID = 1L;
	protected static final int DEFAULT_MARGIN = 50;
	
	// the SlideRule panel for the call back
	private SlideRulePanel srp;
	
	// the left and right slider
	private JSlider[] slider;
	
	MarginPanel(SlideRulePanel srp) {
		super(new GridLayout(1,3));
		this.srp = srp;
		
		// the two sliders
		slider = new JSlider[2];
		for(int i = 0; i < 2; ++i) {
			slider[i] = new JSlider(DEFAULT_MARGIN, 200, DEFAULT_MARGIN);
			slider[i].setMajorTickSpacing(50);
			slider[i].setMinorTickSpacing(10);
			slider[i].setPaintTicks(true);
			Hashtable<?, ?> dic = slider[i].createStandardLabels(50);
			slider[i].setLabelTable(dic);
			slider[i].setPaintLabels(true);
			slider[i].addChangeListener(this);
		}
		// the second slider is inverted
		slider[1].setInverted(true);
		// the two labels
		add(slider[0]);
		JLabel label = new JLabel("Left and right margin in pixels");
		label.setHorizontalAlignment(SwingConstants.CENTER);
		add(label);
		add(slider[1]);
		setBorder(BorderFactory.createLineBorder(Color.BLACK));
		
	}

	// called when a slider changes
	public void stateChanged(ChangeEvent e) {
		JSlider s = (JSlider) e.getSource();
		int value = s.getValue();
		// check if it is the left or righ slider that was changed
		if(s == slider[0]) {
			srp.setLeftMargin(value);
		}
		else {
			srp.setRightMargin(value);
		}
		srp.repaint();
	}

}



Two utility classes that store a text to display or a tick to display.
They receive as parameter a String and a value (from which we will compute the log10)
The TextAndLog contains a String (basically ascii representation of numbers) and its log10 value.
The TickAndLog contains the log10 value and the size of the tick.
These two class have a computePixel() method. When the JPanel needs to be refresh the width of the JPanel
in pixels is passes as parameter to this method. The X position on the screen is then compute based to the width (in pixels) of the JPanel.
This pixel is saved in an instance variable pixelX that will be used by the paint() method of the main JPanel.

TextAndLog.java

/*
 * Contains the text "number" to be displayed and its log10 
 * corresponding to its postion
 */
public class TextAndLog {
    private final String text;          // the text to display
    private final double log;			// the log10 value
    private int pixelX;					// the value in pixel
    private int deltaX;					// we will shift the Text for this number of pixel
    
    TextAndLog(String text, double value) {
    	this.text = text;
    	log = Math.log10(value);
    	// of how many pixel w shift to the left to center
    	int ilen = text.length();
    	deltaX = 3;						// default for 1,2,3,4,5,6,7,8,9
    	if(ilen == 3)					// 1.1, 1.2, 1.3,... 1.9
    		deltaX = 5;
    	else if(ilen == 2)				// 10
    		deltaX = 7;
    }
    
    // to set the pixel value based on number of pixels to display the rule
    void computePixel(int nbPix) {
    	pixelX = (int) (log * nbPix) - deltaX;
    }
    
    int getPixel() {
    	return pixelX;
    }
    String getText() {
    	return text;
    }
}


TickAndLog.java

/*
 * Contains a thick mark
 */
public class TickAndLog {
	private int height;			// height in pixel
	private double log;			// log value
	private int pixelX;
	
	/*
	 * Constructor
	 */
	TickAndLog(int height, double value) {
		this.height = height;
		log = Math.log10(value);
	}
	
	// to set the pixel value based on number of pixels to display the rule
    void computePixel(int nbPix) {
    	pixelX = (int) (log * nbPix);
    }
    
    int getPixel() {
    	return pixelX;
    }
    int getHeight() {
    	return height;
    }

}



Now the SlideRuleMath.java class.
This class is called once at initialisation. It stores in TextAndLog and TickAndLog arrays all the data to be displayed in the SlideRule.
It also have the method setSize() that receives the width of the main JPanel.
It remembers the width that was used at last call. If the values are the same, noting is done.
If the widht is different, all the arrays of TickAndLog and TextAndLog are scanned to compute the new position (based on log10) of that element in the JPanel.
If you want to add new scales (pixels per inch?), it is here that it should be done.

import java.util.*;
/*
 * Contains the math for all the logs which are basically which log10 to use
 * and the conversion in Pixel
 */
public class SlideRuleMath {

	// the tick length
	private static final int LARGE_TICK = 10, MEDIUM_TICK = 6, SMALL_TICK = 4;
	// we always return array for ferformance reason
	private TextAndLog[] bigText;
	private TextAndLog[] smallText;
	private TickAndLog[] tick;
	private TextAndLog[] x2Text;
	private TickAndLog[] x2Tick;
	private TextAndLog[] p26Text;
	private TickAndLog[] p26Tick;
	private TextAndLog[] p21Text;
	private TickAndLog[] p21Tick;
	private TextAndLog[] p34Text;
	private TickAndLog[] p34Tick;
	
	// for the green area of the sliding part have to remember the first tick and the last one
	private TickAndLog firstTick, lastTick;
	
	// the size in pixel
	private int size = -1;
	
	SlideRuleMath() {
		// arrayList to store TextAndLog and TickAndLog
		// we could have used arrays but to lazy to count the size of them
		ArrayList<TextAndLog> alText = new ArrayList<TextAndLog>();	
		ArrayList<TickAndLog> alTick = new ArrayList<TickAndLog>();

		
		// the major number 1-10 
		for(int i = 1; i <= 10; ++i) {
			double x = (double) i;
			// the text
			alText.add(new TextAndLog("" + i, x));
			// the large tick
			TickAndLog tal = new TickAndLog(LARGE_TICK, x);
			// add to ArrayList
			alTick.add(tal);
		}
		// remember the first and last tick
		firstTick = alTick.get(0);
		lastTick = alTick.get(alTick.size() - 1);
		// convert the big text into an array
		bigText = new TextAndLog[alText.size()];
		bigText = alText.toArray(bigText);
		
		// the smaller text 1.1 to 1.9
		alText = new ArrayList<TextAndLog>();		
		for(int i = 1; i <= 9; i++) {
			double x = i;
			x /= 10;
			x += 1.0;
			alText.add(new TextAndLog("1." + i, x));
			alTick.add(new TickAndLog(LARGE_TICK, x));
		}
		// save in an array
		smallText = new TextAndLog[alText.size()];
		smallText = alText.toArray(smallText);		
		
		// other major ticks from 2.5, 3.5, 4.5, ... 9.5
		for(int i = 25; i <= 95; i += 10) {
			double x = i;
			x /= 10.0;
			alTick.add(new TickAndLog(LARGE_TICK, x));
		}
		
		// medium from 21 to 99
		for(int i = 21; i <= 99; ++i) {
			// I already a large at each multiple of 5
			if(i % 10 == 5)
				continue;
			double x = i;
			x /= 10.0;
			alTick.add(new TickAndLog(MEDIUM_TICK, x));
		}
		
		// a medium at 1.05, 1.10, 1.015
		for(int i = 105; i <= 195; i+= 10) {
			double x = i;
			x /= 100.0;
			alTick.add(new TickAndLog(MEDIUM_TICK, x));
		}
		
		// a small one from 1.01 to 1.99
		for(int i = 101; i <= 199; ++i) {
			// already have one at 105 (medium) and 110 (large)
			if(i % 5 == 0)
				continue;
			double x = i;
			x /= 100.0;
			alTick.add(new TickAndLog(SMALL_TICK, x));
		}
		
		// a small one from 2.1 to 3.9
		for(int i = 205; i <= 395; i+= 5) {
			// already have one at 2.5 (medium) and 3 (large)
			if(i % 10 == 0)
				continue;
			double x = i;
			x /= 100.0;
			alTick.add(new TickAndLog(SMALL_TICK, x));			
		}
		
		// build the tick array
		tick = new TickAndLog[alTick.size()];
		tick = alTick.toArray(tick);
		
		// the X^2 rule
		computeX2();
        // the 640X480 that has 21 pixels per centimeter
		compute640X480();
        // the 800X600 that has 26 pixels per centimeter
		compute800X600();
        // the 1024X768 that has 34 pixels per centimeter
		compute1024X768();
	}
	
	// the 640X480
	private void compute640X480() {
		// the Pixel rules on a 640X480 screen there are 21 pixels per cm
		// the 2 to 10 bigger one
		ArrayList<TextAndLog> pixelTxt = new ArrayList<TextAndLog>();
		ArrayList<TickAndLog> pixelTick = new ArrayList<TickAndLog>();
		// the first one is 21
		pixelTxt.add(new TextAndLog("21", 1.0));
		for(int i = 25; i < 100; i+= 5) {
			double x = i;
			x /= 21.0;
			pixelTick.add(new TickAndLog(LARGE_TICK, x));
			pixelTxt.add(new TextAndLog("" + i, x));	
		}
		// from 1 to 100 we put a tick at each unit
		for(int i = 21; i < 100; ++i) {
			if(i % 5 == 0)
				continue;
			double x = i;
			x /= 21.0;
			pixelTick.add(new TickAndLog(MEDIUM_TICK, x));
		}
		// from 100 to 210
		// - a large tick and text every 25
		// - a medium tick every 5
		// - a small tick at every unit until 150
		for(int i = 100; i <= 210; ++i) {
			double x = i;
			x /= 21.0;
			if(i % 25 == 0) {
				pixelTick.add(new TickAndLog(LARGE_TICK, x));
				pixelTxt.add(new TextAndLog("" + i, x));				
			}
			else if(i % 5 == 0) {
				pixelTick.add(new TickAndLog(MEDIUM_TICK, x));	
			}
			else {
				if(i < 150)
					pixelTick.add(new TickAndLog(SMALL_TICK, x));	
			}
		}
		
		// convert to array
		p21Text = new TextAndLog[pixelTxt.size()];
		p21Text = pixelTxt.toArray(p21Text);
		p21Tick = new TickAndLog[pixelTick.size()];
		p21Tick = pixelTick.toArray(p21Tick);
		
	}
	
	// the 800X800
	private void compute800X600() {
		// the Pixel rules on a 800X600 screen there are 26 pixels per cm
		// the 2 to 10 bigger one
		ArrayList<TextAndLog> pixelTxt = new ArrayList<TextAndLog>();
		ArrayList<TickAndLog> pixelTick = new ArrayList<TickAndLog>();
		// the first one is 26
		pixelTxt.add(new TextAndLog("26", 1.0));
		for(int i = 30; i < 100; i+= 5) {
			double x = i;
			x /= 26.0;
			pixelTick.add(new TickAndLog(LARGE_TICK, x));
			pixelTxt.add(new TextAndLog("" + i, x));	
		}
		// from 1 to 100 we put a tick at each unit
		for(int i = 26; i < 100; ++i) {
			if(i % 5 == 0)
				continue;
			double x = i;
			x /= 26.0;
			pixelTick.add(new TickAndLog(MEDIUM_TICK, x));
		}
		
		// from 100 to 160
		// - a large tick and text every 25
		// - a medium tick every 5
		// - a small tick every unit up to 150
		for(int i = 100; i <= 250; ++i) {
			double x = i;
			x /= 26;
			if(i % 25 == 0) {
				pixelTick.add(new TickAndLog(LARGE_TICK, x));
				pixelTxt.add(new TextAndLog("" + i, x));
			}
			else if(i % 5 == 0) {
				pixelTick.add(new TickAndLog(MEDIUM_TICK, x));				
			}
			else {
				if(i < 150)
					pixelTick.add(new TickAndLog(SMALL_TICK, x));				
			}
		}
		
		// convert to array
		p26Text = new TextAndLog[pixelTxt.size()];
		p26Text = pixelTxt.toArray(p26Text);
		p26Tick = new TickAndLog[pixelTick.size()];
		p26Tick = pixelTick.toArray(p26Tick);		
	}
	
	// the 1024X768
	private void compute1024X768() {
		// the Pixel rules on a 1024X768 screen there are 34 pixels per cm
		// the 2 to 10 bigger one
		ArrayList<TextAndLog> pixelTxt = new ArrayList<TextAndLog>();
		ArrayList<TickAndLog> pixelTick = new ArrayList<TickAndLog>();
		for(int i = 40; i < 100; i+= 5) {
			double x = i;
			x /= 34.0;
			pixelTick.add(new TickAndLog(LARGE_TICK, x));
			pixelTxt.add(new TextAndLog("" + i, x));	
		}
		// the first one is 34
		pixelTxt.add(new TextAndLog("34", 1.0));
		// from 1 to 100 we put a tick at each unit
		for(int i = 34; i < 100; ++i) {
			if(i % 5 == 0)
				continue;
			double x = i;
			x /= 34.0;
			pixelTick.add(new TickAndLog(MEDIUM_TICK, x));
		}
		// from 100 to 160
		// - a large tick and text every 25
		// - a medium tick every 5
		// - a small tick every unit up to 150
		for(int i = 100; i <= 340; ++i) {
			double x = i;
			x /= 34;
			if(i % 25 == 0) {
				pixelTick.add(new TickAndLog(LARGE_TICK, x));
				pixelTxt.add(new TextAndLog("" + i, x));
			}
			if(i % 5 == 0) {
				pixelTick.add(new TickAndLog(MEDIUM_TICK, x));
			}
			else {
				if(i < 150)
					pixelTick.add(new TickAndLog(SMALL_TICK, x));
			}
		}
		
		// convert to array
		p34Text = new TextAndLog[pixelTxt.size()];
		p34Text = pixelTxt.toArray(p34Text);
		p34Tick = new TickAndLog[pixelTick.size()];
		p34Tick = pixelTick.toArray(p34Tick);		
	}
	

	// the X^2
	private void computeX2() 
	{
		// the text for X^2 and as the same value as the other one but we display X2 instead
		// first batch is fom 1 to 9 then from 10 to 100
		ArrayList<TextAndLog> x2AlTxt = new ArrayList<TextAndLog>();
		ArrayList<TickAndLog> x2AlTick = new ArrayList<TickAndLog>();
		for(int i = 1; i <= 9; ++i) {
			double x = i;
			x = Math.sqrt(x);
			x2AlTick.add(new TickAndLog(LARGE_TICK, x));			
			x2AlTxt.add(new TextAndLog("" + i, x));
		}
		for(int i = 10; i <= 100; i += 10) {
			double x = i;
			x = Math.sqrt(x);
			x2AlTick.add(new TickAndLog(LARGE_TICK, x));	
			// horrible patch: for x == 100 we have to shift a bit to the left before drawing
			if(i == 100)
				x -= 0.1;
		    x2AlTxt.add(new TextAndLog("" + i, x));	
		}
		
		// now the ticks marks
		// for 1 to 2 (100-200)
		for(int i = 100; i < 200; i += 5) {
			if(i == 100)				// already done
				continue;
			double x = i;
			x /= 100;
			x = Math.sqrt(x);
			if(i % 50 == 0)
				x2AlTick.add(new TickAndLog(LARGE_TICK, x));
			else if(i % 25 == 0)
				x2AlTick.add(new TickAndLog(MEDIUM_TICK, x));
			else
				x2AlTick.add(new TickAndLog(SMALL_TICK, x));
		}
		// from 2 to 6
		// from 21 to 59 every 1/10
		for(int i = 21; i <= 59; ++i) {
			if(i % 10 == 0)					// already done
				continue;
			double x = i;
			x /= 10;
			x = Math.sqrt(x);
			if(i % 5 == 0)
				x2AlTick.add(new TickAndLog(LARGE_TICK, x));
			else
				x2AlTick.add(new TickAndLog(MEDIUM_TICK, x));
		}
		
		// from 6 to 10
		// from 600 to 999 step 25
		for(int i = 600; i < 1000; i += 25) {
			if(i % 100 == 0)				// already done
				continue;
			double x = i;
			x /= 100.0;
			x = Math.sqrt(x);
			if(i % 50 == 0)
				x2AlTick.add(new TickAndLog(MEDIUM_TICK, x));
			else
				x2AlTick.add(new TickAndLog(SMALL_TICK, x));
			
		}
		
		// from 10 to 20
		// 100 to 200 each .5 unit
		for(int i = 100; i < 200; i += 5) {
			if(i == 100)						// already done but we start at 100 for += 5
				continue;
			double x = i;
			x /= 10.0;
			x = Math.sqrt(x);
			if(i % 50 == 0)
			   x2AlTick.add(new TickAndLog(LARGE_TICK, x));
			else if(i % 25 == 0)
			   x2AlTick.add(new TickAndLog(MEDIUM_TICK, x));
			else
				x2AlTick.add(new TickAndLog(SMALL_TICK, x));	
		}
		// from 20 to 50 
		// 20 to 50 each unit
		for(int i = 21; i < 50; ++i) {
			if(i % 10 == 0)
				continue;
			double x = i;
			x = Math.sqrt(x);
			if(i % 5 == 0)
			   x2AlTick.add(new TickAndLog(MEDIUM_TICK, x));
			else
				x2AlTick.add(new TickAndLog(SMALL_TICK, x));	
		}
		// for the rest 50 to 100
		// 500 to 1000 each 25
		for(int i = 500; i < 1000; i += 25) {
			if(i % 100 == 0)
				continue;
			double x = i;
			x /= 10.0;
			x = Math.sqrt(x);
			if(i % 50 == 0)
				   x2AlTick.add(new TickAndLog(MEDIUM_TICK, x));
				else
					x2AlTick.add(new TickAndLog(SMALL_TICK, x));	
		}
		
		// convert to array
		x2Text = new TextAndLog[x2AlTxt.size()];
		x2Text = x2AlTxt.toArray(x2Text);
		x2Tick = new TickAndLog[x2AlTick.size()];
		x2Tick = x2AlTick.toArray(x2Tick);	
		
		
	}
	// informs the math the size to use
	void setSize(int size) {
		// if already calculated
		if(this.size == size)
			return;
		this.size = size;
		// recalculate the pixel of our arrays (the x2 can be done at the same time)
		for(int i = 0; i < bigText.length; ++i)
			bigText[i].computePixel(size);
		for(int i = 0; i < smallText.length; ++i)
			smallText[i].computePixel(size);
		for(int i = 0; i < tick.length; ++i)
			tick[i].computePixel(size);
		for(int i = 0; i < x2Text.length; ++i)
			x2Text[i].computePixel(size);
		for(int i = 0; i < x2Tick.length; ++i)
			x2Tick[i].computePixel(size);
		for(int i = 0; i < p26Text.length; ++i)
			p26Text[i].computePixel(size);
		for(int i = 0; i < p26Tick.length; ++i)
			p26Tick[i].computePixel(size);
		for(int i = 0; i < p21Text.length; ++i)
			p21Text[i].computePixel(size);
		for(int i = 0; i < p21Tick.length; ++i)
			p21Tick[i].computePixel(size);
		for(int i = 0; i < p34Text.length; ++i)
			p34Text[i].computePixel(size);
		for(int i = 0; i < p34Tick.length; ++i)
			p34Tick[i].computePixel(size);
	}

	/**
	 * @return the bigText
	 */
	protected final TextAndLog[] getBigText() {
		return bigText;
	}

	/**
	 * @return the smallText
	 */
	protected final TextAndLog[] getSmallText() {
		return smallText;
	}

	protected final TickAndLog[] getTick() {
		return tick;
	}

 	/**
	 * @return the firstTick
	 */
	protected final TickAndLog getFirstTick() {
		return firstTick;
	}

	/**
	 * @return the lastTick
	 */
	protected final TickAndLog getLastTick() {
		return lastTick;
	}
	
	protected final TextAndLog[] getX2Text() {
		return x2Text;
	}
	protected final TickAndLog[] getX2Tick() {
		return x2Tick;
	}
	protected final TextAndLog[] getP26Text() {
		return p26Text;
	}
	protected final TickAndLog[] getP26Tick() {
		return p26Tick;
	}
	protected final TextAndLog[] getP21Text() {
		return p21Text;
	}
	protected final TickAndLog[] getP21Tick() {
		return p21Tick;
	}
	protected final TextAndLog[] getP34Text() {
		return p34Text;
	}
	protected final TickAndLog[] getP34Tick() {
		return p34Tick;
	}
}



Finally, the main JPanel that display the whole thing.
It utilizes a null layout because Oracle didn't come with a LogarithmicLayout yet :)

This class is a bit long (a very long paint() method), I should think of splitting it.
May be a merge of Math and Drawing should be done. I will eventually

SlideRulePanel.java

import javax.swing.*;

import java.awt.*;
import java.awt.event.*;

public class SlideRulePanel extends JPanel implements MouseMotionListener, ActionListener {

	private static final long serialVersionUID = 1L;

	// size of the buttons and JLabel
	private static final Dimension buttonSize = new Dimension(20, 20);
	private static final Dimension labelSize = new Dimension(30, 18);

	// Y positions for the elements related from one to the other
	private static final int CURSOR_TOP = 50;
	private static final int LOG10_Y = CURSOR_TOP + 25;
	private static final int CURSOR_BOTTOM = CURSOR_TOP + 300;
	private static final int TEXT_X2_Y = LOG10_Y + 40;
	// the + and - buttons
	private static final int TOP_BUTTON_Y = CURSOR_TOP - (buttonSize.height / 2);
	private static final int BOTTOM_BUTTON_Y = CURSOR_BOTTOM - (buttonSize.height / 2);
	
	// the text on the sliding and fixed rules
	private static final int TEXT_SLIDING_Y = TEXT_X2_Y + 50;
	private static final int TEXT_FIX_Y = TEXT_SLIDING_Y + 35;
	private static final int TICK_SLIDING_Y = TEXT_SLIDING_Y + 10;
	private static final int TICK_FIX_Y = TICK_SLIDING_Y + 1;
	
	// the green sliding recrangle
	private static final int GREEN_SLIDING_Y = TEXT_SLIDING_Y - 15;
	private static final int GREEN_SLIDING_HEIGHT = TICK_FIX_Y - TEXT_SLIDING_Y + 15;
	
	// the pixels rules
	private static final int PIXEL_640X480_H = TICK_FIX_Y + 60;	
	private static final int PIXEL_800X600_H = PIXEL_640X480_H + 35;
	private static final int PIXEL_1024X768_H = PIXEL_800X600_H + 35;

	private static final int SLIDE_LABEL_Y = CURSOR_TOP - (buttonSize.height * 2) + 3; 
	private static final int FIXED_LABEL_Y = CURSOR_BOTTOM + (buttonSize.height / 2) + 3; 
	private static final int CURSOR_LABEL_DELTA_X = -40;

	// for the mouse drag the area that I monitor
	// we will assume that we move the cursor if the mouse is over the bottom of the top button
	private static final int TOP_AREA_Y = TOP_BUTTON_Y + buttonSize.height + 5;
	// or under the top of the bottom buton
	private static final int BOTTOM_AREA_Y = BOTTOM_BUTTON_Y - 5;
	// the center area where are the slider and the fix area
	private static final int CENTER_AREA_TOP_Y = GREEN_SLIDING_Y - 5;
	private static final int CENTER_AREA_BOTTOM_Y = GREEN_SLIDING_Y + (2 * GREEN_SLIDING_HEIGHT) + 5;

	// light green color to cover the sliding part
	private static final Color lightGreen = new Color(225, 255, 190);
	// dark green color for the curson
	private static final Color darkGreen = new Color(0, 225, 0);
	private static final Color darkDarkGreen = new Color(0, 100, 0);
	// light yellow to put in the back of the label
	private static final Color lightYellow = new Color(255, 255, 125);
	// the math to convert to pixels and get the arrays
	private SlideRuleMath srm;
	// arrays of smalText and bigText of numbers displayed on the rule
	private TextAndLog[] smallText;
	private TextAndLog[] bigText;
	// array of ticks
	private TickAndLog[] tick;
	// the X2
	private TextAndLog[] x2Text;
	private TickAndLog[] x2Tick;
	// the 640X480 800X600 1024X768
	private TextAndLog[] p21Text, p26Text, p34Text;
	private TickAndLog[] p21Tick, p26Tick, p34Tick;

	// to save the Graphics standard Font
	private Font standardFont;
	
	// to display regular numbers and the 1.1, 1.2, ... 1.9
	private Font bigFont, smallFont;
	// the margin where the slider can slide
	private int leftMargin, rightMargin;
	// from where we draw the slider
	private int leftmostPixel = 0;
	// last mouse X position
	private int lastMouseX;
	// last cursor position
	private int lastCursorX = -1;
	// the 2 buttons for fine tune
	private JButton[] bPlus, bMinus;
	// the labels for the log10 the X2 and the 3 pixels per centimeter rules
	private static final int LOG10LABEL = 0, X2LABEL = 1, P21LABEL = 2, P26LABEL = 3, P34LABEL = 4; 
	private JLabel[] valueLabel = new JLabel[P34LABEL + 1];
	// the value of the fixed part (used by others once computed)
	private double dFixed, dSlide;
	// the labels for the cursor value
	private JLabel fixedLabel, slideLabel;
	// the last width
	private int lastWidth = 1;
	
	SlideRulePanel(SlideRuleMath srm) {
		super(null);
		setBackground(Color.WHITE);
		this.srm = srm;
		leftMargin = rightMargin = MarginPanel.DEFAULT_MARGIN;
		
		// fetch the arrays of text and ticks to display
		fetchArraysFromMath();
		
		// build the fonts from standard font of JLabel
		JLabel label = new JLabel();
		bigFont = label.getFont();
		float size = bigFont.getSize2D();
		size *= 0.72f;
		smallFont = bigFont.deriveFont(size);

		// build the + and - minus buttons to displace the cursor by 1 pixel
		buildPlusMinusButtons();
		
		// the label put over and under the cursor
		fixedLabel = new JLabel("");
		slideLabel = new JLabel("");
		initCursorLabel(fixedLabel);
		initCursorLabel(slideLabel);

		// the value label for the log10 and the x2 and the 3 pixels rules
		for(int i = 0; i < valueLabel.length; ++i) {
			valueLabel[i] = new JLabel();
			valueLabel[i].setHorizontalAlignment(SwingConstants.CENTER);
			valueLabel[i].setFont(smallFont);
			valueLabel[i].setSize(labelSize);
			add(valueLabel[i]);
			
		}
		
		// mouse listener when we want to slide the sliding part
		addMouseMotionListener(this);			
	}
	/*
	 * Get the arrays from math
	 */
	private void fetchArraysFromMath() {
		smallText = srm.getSmallText();
		bigText = srm.getBigText();
		tick = srm.getTick();
		x2Text = srm.getX2Text();
		x2Tick = srm.getX2Tick();
		// historicaly they are name 21, 26, 34 which is the nunber of pixels 
		// per centimeter for the 3 resolutions
		p21Text = srm.getP21Text();	
		p21Tick = srm.getP21Tick();
		p26Text = srm.getP26Text();	
		p26Tick = srm.getP26Tick();
		p34Text = srm.getP34Text();	
		p34Tick = srm.getP34Tick();
	}

	private void buildPlusMinusButtons() {
		// the 2 buttons arrays for 1 pixel +/i
		Insets insets = new Insets(0, 0, 0, 0);
		bPlus = new JButton[2];
		bMinus = new JButton[2];
		bPlus[0] = new JButton("+");
		bPlus[1] = new JButton("+");
		bMinus[0] = new JButton("-");
		bMinus[1] = new JButton("-");
		// we need a larger font
		Font font = bPlus[0].getFont();
		float size = font.getSize2D();
		size *= 1.2f;
		font = font.deriveFont(size);
		for(int i = 0; i < 2; ++i) {
			bPlus[i].setMargin(insets);
			bMinus[i].setMargin(insets);
			bPlus[i].setBackground(Color.WHITE);     // to be transparent to the background
			bMinus[i].setBackground(Color.WHITE);
			bPlus[i].setSize(buttonSize);
			bMinus[i].setSize(buttonSize);
			bPlus[i].addActionListener(this);
			bMinus[i].addActionListener(this);
	        bPlus[i].setFont(font);
	        bMinus[i].setFont(font);
	        bPlus[i].setFocusPainted(false);      // don't show the horrible square that show that the 
	        bMinus[i].setFocusPainted(false);     // button has the focus
	        add(bPlus[i]);
	        add(bMinus[i]);
		}
	}
	/*
	 * To init each of the 2 labels on top of the cursor
	 */
	private void initCursorLabel(JLabel label) {
		Font font = label.getFont();
		float size = font.getSize2D();
		size *= 1.2f;
		font = font.deriveFont(size);
		label.setFont(font);
		label.setOpaque(true);
		label.setHorizontalAlignment(SwingConstants.CENTER);
		label.setSize(80, 24);
		label.setForeground(darkDarkGreen);
		label.setBackground(lightYellow);
		label.setBorder(BorderFactory.createLineBorder(Color.BLACK));
		add(label);
		
	}
	/**
	 * @param leftMargin the leftMargin to set
	 */
	protected final void setLeftMargin(int leftMargin) {
		this.leftMargin = leftMargin;
	}

	/**
	 * @param rightMargin the rightMargin to set
	 */
	protected final void setRightMargin(int rightMargin) {
		this.rightMargin = rightMargin;
	}
	
	@Override
	public void mouseDragged(MouseEvent e) {
		int y = e.getY();
		// if not in a monitored area
		if(!mouseInMonitoredArea(y)) {
			lastMouseX = -1;
			return;				// do nothing
		}
		int x = e.getX();
		// if the user just entered the area dragging the mouse
		if(lastMouseX == -1) {
			lastMouseX = x;
			return;
		}
		
		// determine the change to apply
		int deltaX = x - lastMouseX;
		lastMouseX = x;
		
		// depending if cursor or sliding
		if(y < TOP_AREA_Y || y > BOTTOM_AREA_Y) {
			lastCursorX += deltaX;
			// don't let the cursor going under 0
			if(lastCursorX < 0)
				lastCursorX = 0;
		}
		else {
			leftmostPixel += deltaX;
		}
		repaint();
	}

	@Override
	public void mouseMoved(MouseEvent e) {
		// saved for mouse listener if in a valid area
		if(mouseInMonitoredArea(e.getY())) 
			lastMouseX = e.getX();	
		else 
			lastMouseX = -1;			// flag mouse is outside the controled area
	}
	
	/*
	 * return true if the mouse is in the monitored area
	 */
	private boolean mouseInMonitoredArea(int y) {
		// in the cursor area
		if(y < TOP_AREA_Y)
			return true;
		if(y > BOTTOM_AREA_Y)
			return true;
		// in the sliding area
		return y > CENTER_AREA_TOP_Y && y < CENTER_AREA_BOTTOM_Y;
	}
	
	

	// the buttons +/- to slide for 1 pixel
	public void actionPerformed(ActionEvent e) {
		JButton o = (JButton) e.getSource();
		
		if(o == bPlus[0] || o == bPlus[1])
			++lastCursorX;
		else
			--lastCursorX;
	
		repaint();
	}

	public void paint(Graphics g) {
		standardFont = g.getFont();
		// the width for the fixed part
		int width = getWidth();
		// move the cursor to follow frame size
		// first call
		if(lastWidth == -1) {
			lastWidth = width;
		}
		else if(lastWidth != width) {
			if(lastCursorX != -1) {
				int delta = width - lastWidth;
				lastCursorX += delta;
			}
			lastWidth = width;
		}
		
		int centerWidth = width - (leftMargin + rightMargin);
		// call super for the backGround and the slider
		super.paint(g);
		// first call we center the cursor
		if(lastCursorX == -1)
			lastCursorX = centerWidth / 2;
		// make sure the cursor is visible
		if(lastCursorX > centerWidth)
			lastCursorX = centerWidth;
		
				
		// computes the log in pixels
		srm.setSize(centerWidth);
		// we do not allow the sliding part to slide outside the center part
		if(leftmostPixel > centerWidth)
			leftmostPixel = centerWidth;
		else if(leftmostPixel < -centerWidth)
			leftmostPixel = -centerWidth;
		// fetch the first tick and last tick position
		int firstX = srm.getFirstTick().getPixel() - 8 + leftmostPixel + leftMargin;
		int lastX = srm.getLastTick().getPixel() + 12 + leftmostPixel + leftMargin;
		// a black line over it 1 pixel wider on the left and on the right
		int yFrom = GREEN_SLIDING_Y - 1;
		int yTo = yFrom + GREEN_SLIDING_HEIGHT + 1;
		g.drawLine(firstX - 1, yFrom, lastX + 1, yFrom);
		// the left and right side
		g.drawLine(firstX - 1, yFrom, firstX - 1, yTo);
		g.drawLine(lastX + 1, yFrom, lastX + 1, yTo);
		
		// the green part of the sliding 
		g.setColor(lightGreen);
		g.fillRect(firstX, GREEN_SLIDING_Y, lastX - firstX, GREEN_SLIDING_HEIGHT);
		// put it back black
		g.setColor(Color.BLACK);
		g.setFont(bigFont);
		
		// now the rectangle under the fixed part
		yFrom += GREEN_SLIDING_HEIGHT + 1;
		yTo += GREEN_SLIDING_HEIGHT + 1;
		firstX -= leftmostPixel;
		lastX -= leftmostPixel;
		g.drawLine(firstX - 1, yTo, lastX + 1, yTo);
		// the left and right side
		g.drawLine(firstX - 1, yFrom, firstX - 1, yTo);
		g.drawLine(lastX + 1, yFrom, lastX + 1, yTo);		
		
				
		// the big text on both the slider and the fix part
		for(int i = 0; i < bigText.length; ++i) {
			String text;
			int x;		
			// the one in the rules
			text = bigText[i].getText();
			x = bigText[i].getPixel() + leftMargin;
			// on the fixed part
			g.drawString(text, x, TEXT_FIX_Y);
			// on the sliding part
			x += leftmostPixel;
			g.drawString(text, x, TEXT_SLIDING_Y);
		}
		// the small text 
		g.setFont(smallFont);
		for(int i = 0; i < smallText.length; ++i) {
			String text = smallText[i].getText();
			int x = smallText[i].getPixel() + leftMargin;
			// on the fixed part
			g.drawString(text, x, TEXT_FIX_Y);
			// on the sliding part
			x += leftmostPixel;
			g.drawString(text, x, TEXT_SLIDING_Y);
		}
		// the tick
		for(int i = 0; i < tick.length; ++i) {
			TickAndLog tal = tick[i];
			int height = tal.getHeight();
			int x = tal.getPixel() + leftMargin;
			// on the fixed part
			g.drawLine(x, TICK_FIX_Y, x, TICK_FIX_Y + height);
			// on the sliding part
			x += leftmostPixel;
			g.drawLine(x, TICK_SLIDING_Y, x, TICK_SLIDING_Y - height);
		}
		
		// drawing the cursor and other scales
		drawCursor(g, centerWidth);
		fixedLabel.setText(String.format("%.3f", dFixed));
		slideLabel.setText(String.format("%.3f", dSlide));
		drawOtherScales(g, centerWidth);
	}

	/*
	 * Drawing the cursor and setting the values
	 * of dFixed and dSlide
	 */
	private void drawCursor(Graphics g, int width) {
		// the 2 values for slidind and fixed
		// width and position in double
		double dWidth = width;
		double dX = lastCursorX + 1.0;
		
		// the cursor is in green
		g.setColor(darkGreen);

		int x = lastCursorX + leftMargin;
		// the top and bottom
		g.fillRect(x-6, CURSOR_TOP, 13, 4);
		g.fillRect(x-6, CURSOR_BOTTOM, 13, 4);
		// the line that joins them
		g.drawLine(x, CURSOR_TOP, x, CURSOR_BOTTOM);
		
		// position the JButtons
		int leftX = x - 16 - buttonSize.width;
		int rightX = x + 14;
		bPlus[0].setLocation(rightX, TOP_BUTTON_Y);
		bPlus[1].setLocation(rightX, BOTTOM_BUTTON_Y);
		bMinus[0].setLocation(leftX, TOP_BUTTON_Y);
		bMinus[1].setLocation(leftX, BOTTOM_BUTTON_Y);
		
		slideLabel.setLocation(x + CURSOR_LABEL_DELTA_X, SLIDE_LABEL_Y);
		fixedLabel.setLocation(x + CURSOR_LABEL_DELTA_X, FIXED_LABEL_Y);
		
		// the values but lets take the extreme first
		if(lastCursorX == 0) {
			dFixed = 1.0;
			valueLabel[LOG10LABEL].setText("0.000");
		} else if(lastCursorX == width - 1) {
			valueLabel[LOG10LABEL].setText("1.000");
			dFixed = 10.0;
		}
		else {
			double ratio = dX / dWidth;
			String format = String.format("%.3f", ratio);
			valueLabel[LOG10LABEL].setText(format);
			dFixed = Math.pow(10.0, ratio);
		}
		// if both rules are the same
		if(leftmostPixel == 0) {
			dSlide = dFixed;
            return;			
		}
		
		// compute the sliding one
		dX -= leftmostPixel;
		double ratio = dX / dWidth;
		dSlide = Math.pow(10.0, ratio);
	}

	// the other scales in the fixed rule
	private void drawOtherScales(Graphics g, int width) {
		g.setFont(standardFont);
		g.setColor(Color.BLACK);
		int lastX = srm.getLastTick().getPixel() + 12 + leftmostPixel + leftMargin;

		// the X2 scale on the fix part
		int x = 0;     // init to 0 to avoid computer warning "x may not be initialized"
		
		for(int i = 0; i < x2Text.length; ++i) {
			String text = x2Text[i]. getText();
			x = x2Text[i].getPixel() + leftMargin;
			// on the fixed part
			g.drawString(text, x, TEXT_X2_Y);
		}
		x = drawTickAt(g, x2Tick, TEXT_X2_Y);
        // the x2 logo
        x += 25;
        g.drawString("x", x, TEXT_X2_Y + 4);
        x += 5;
        g.setFont(smallFont);
        g.drawString("2", x, TEXT_X2_Y - 4);
        valueLabel[X2LABEL].setText(String.format("%.2f", (dFixed * dFixed)));
		valueLabel[X2LABEL].setLocation(x - 5, TEXT_X2_Y + 4);
        
	
        // the LOG10 scale
		g.setFont(smallFont);
		g.setColor(Color.BLACK);
		// the log10 scale that goes from 0 to 10 from the beginning of the center panel
		// this one is easy no log to apply
		double dWidth = width;
		// the offset from each tag
		double offset = dWidth / 10.0;
		// the text part of the scale
		for(int i = 0; i <= 10; ++i) {
			x = (int) (i * offset);
			// we put .i for numbers 1 t 9 and just 1 for 10
			if(i < 10)
				g.drawString("." + i, x + leftMargin - 4, LOG10_Y);
			else
				g.drawString(" 1", x + leftMargin - 4, LOG10_Y);
				
		}

		// now the tick
		int y = LOG10_Y + 14;
		offset = dWidth / 1000.0;
		for(int i = 0; i <= 1000; i += 5) {
			x = (int) (i * offset) + leftMargin;
			// determine the length of the tick mark
			int length = 4;         // default value
			if(i % 100 == 0)        // at 10 unit
				length = 10;
			else if(i % 50 == 0)
				length = 8;
			else if(i % 25 == 0)
				length = 4;
			g.drawLine(x, y, x, y - length);
		}
		
		// put the log10 text and the value at the end
		// these are easy no log involved we can do it on the fly
		g.drawString("log10", lastX , LOG10_Y + 4);		
		valueLabel[LOG10LABEL].setLocation(lastX , LOG10_Y + 4);
		
		// the 640X480 rule
		drawPixelText(g, p21Text, PIXEL_640X480_H);
        x = drawTickAt(g, p21Tick, PIXEL_640X480_H);
        g.drawString("640X480", lastX, PIXEL_640X480_H);
        valueLabel[P21LABEL].setText(String.format("%.1f", (dFixed * 21.0)));
        valueLabel[P21LABEL].setLocation(lastX, PIXEL_640X480_H + 4);
		
		// the 800X600 rule
		drawPixelText(g, p26Text, PIXEL_800X600_H);
        x = drawTickAt(g, p26Tick, PIXEL_800X600_H);
        g.drawString("800X600", lastX, PIXEL_800X600_H);
        valueLabel[P26LABEL].setText(String.format("%.1f", (dFixed * 26.0)));
        valueLabel[P26LABEL].setLocation(lastX, PIXEL_800X600_H + 4);
		
		// the 1024X768 rule
		drawPixelText(g, p34Text, PIXEL_1024X768_H);
        x = drawTickAt(g, p34Tick, PIXEL_1024X768_H);
        g.drawString("1024X768", lastX, PIXEL_1024X768_H);
        valueLabel[P34LABEL].setText(String.format("%.1f", (dFixed * 34.0)));
        valueLabel[P34LABEL].setLocation(lastX, PIXEL_1024X768_H + 4);
		
	}
	
	/*
	 * Draw the text for the pixels rules
	 */
	private void drawPixelText(Graphics g, TextAndLog[] textAndLog, int y) {
		for(int i = 0; i < textAndLog.length; ++i) {
			String text = textAndLog[i]. getText();
			int x = textAndLog[i].getPixel() + leftMargin;
			// on the fixed part (horrible patch for 2 digits number)
			if(text.length() == 2)
			    g.drawString(text, x + 2, y);
			else
			    g.drawString(text, x, y);			
		}
		
	}
	/*
	 * draw ticks at a certain Y return the X of the last tick
	 */
	private int drawTickAt(Graphics g, TickAndLog[] tickAndLog, int y) {
		int x = 0;
		y += 12;
	       for(int i = 0; i < tickAndLog.length; ++i) {
	        	TickAndLog tal = tickAndLog[i];
				int height = tal.getHeight();
				x = tal.getPixel() + leftMargin;
				// on the fixed part
				g.drawLine(x, y, x, y - height);
	        }
		
		return x;
	}
}



This post has been edited by pbl: 20 December 2010 - 09:59 PM


Is This A Good Question/Topic? 0
  • +

Page 1 of 1