view src/uk/ac/qmul/eecs/depic/daw/gui/Rule.java @ 2:c0412c81d274

Added documentation
author Fiore Martin <f.martin@qmul.ac.uk>
date Thu, 18 Feb 2016 18:35:26 +0000
parents 3074a84ef81e
children
line wrap: on
line source
/*  
 Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation.

 Copyright (C) 2015  Queen Mary University of London (http://depic.eecs.qmul.ac.uk/)
	
 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 3 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, see <http://www.gnu.org/licenses/>.
*/
package uk.ac.qmul.eecs.depic.daw.gui;

import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Polygon;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import java.awt.geom.Line2D;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.text.DecimalFormat;

import javax.accessibility.Accessible;
import javax.accessibility.AccessibleContext;
import javax.accessibility.AccessibleRole;
import javax.accessibility.AccessibleState;
import javax.accessibility.AccessibleStateSet;
import javax.swing.BorderFactory;
import javax.swing.JComponent;
import javax.swing.event.MouseInputAdapter;

import uk.ac.qmul.eecs.depic.daw.Selection;
import uk.ac.qmul.eecs.depic.daw.SoundWave;

/**
 * 
 * The rule on top of the arrange window. 
 *
 */
public class Rule extends JComponent implements PropertyChangeListener , Accessible {
	private static final long serialVersionUID = 1L;
	public static final int DEFAULT_TICK_INTERVAL = 50;
	public static final int TICK_HEIGHT = 10;
	public static final int HEIGHT = 40;
	public static final int CURSOR_HEIGHT = 10;
	public static final int CURSOR_WIDTH = 20;
	public static final Color LOOP_MARKERS_COLOR = new Color(10,100,30);
	public static final Color CURSOR_COLOR = AudioTrack.WAVE_COLOR;
	private static final DecimalFormat TICK_FORMAT = new DecimalFormat("###.#");
	
	private AudioTrack track;
	private int scaleFactor;
	private Polygon cursor;
	private Polygon loopStartMark;
	private Polygon loopEndMark;
	

	public Rule(){
		Dimension size = new Dimension(AudioTrack.MAX_TRACK_WIDTH,HEIGHT);
		setMaximumSize(size);
		
		setPreferredSize(new Dimension(0,HEIGHT));
		setBorder(BorderFactory.createMatteBorder(0,0,1,0,Color.black));
		
		/* a triangle initially centred on 0*/
		cursor = new Polygon(
					new int[]{0,-CURSOR_WIDTH/2,CURSOR_WIDTH/2}, 
					new int[] {HEIGHT,HEIGHT-CURSOR_HEIGHT,HEIGHT-CURSOR_HEIGHT}, 
					3);
		loopStartMark = new Polygon();
		loopEndMark = new Polygon();

		MouseInteraction mouseInteraction = new MouseInteraction();
		addMouseListener(mouseInteraction);
		addMouseMotionListener(mouseInteraction);
	}
	
	@Override
	public void paintComponent(Graphics g){
		Graphics2D g2 = (Graphics2D)g;
		Color oldColor = g2.getColor();
		
		g2.setColor(getBackground());
        g2.fillRect(0, 0, getWidth(), getHeight());
        g2.setColor(getForeground());
		
		Line2D line = new Line2D.Float();
		int height = getHeight();
		/* draw rule's ticks */
		
		if(track.getSecondsPerPixel() == 0){
			for(int i=DEFAULT_TICK_INTERVAL; i< getSize().width; i += DEFAULT_TICK_INTERVAL ){
				line.setLine(i, height, i, height-TICK_HEIGHT);
				g2.draw(line);
			}
		}else{
			int tickInterval = (int) ( (1/track.getSecondsPerPixel() / 2 ));
			for(int i=tickInterval, j=1; i< getSize().width; i += tickInterval, j++ ){
				if(j % 2 == 1){
					line.setLine(i, height, i, height-TICK_HEIGHT/3);
					g2.draw(line);
				}else{
					line.setLine(i, height, i, height-TICK_HEIGHT);
					g2.draw(line);
					String tick = TICK_FORMAT.format(j/2);//track.getSecondsPerPixel()*i);
					int stringLen = (int)g2.getFontMetrics().getStringBounds(tick, g2).getWidth(); 
					g2.drawString(tick, i-(stringLen/2), height-TICK_HEIGHT-3);
				}
			}
		}
		
		
		/* draw the cursor in WAVE_COLOR */
		g2.setColor(CURSOR_COLOR);
		g2.fill(cursor);
		
		/* draw the loop markers, if any */
		g2.setColor(LOOP_MARKERS_COLOR);
		g2.fill(loopStartMark);
		g2.fill(loopEndMark);
		if(loopEndMark.npoints != 0){
			g2.drawLine(loopStartMark.xpoints[0], 
					HEIGHT - (loopStartMark.getBounds().height/2), 
					loopEndMark.xpoints[0]-1,// with -1 it's drawn it better  
					HEIGHT - (loopEndMark.getBounds().height/2)
				);
		}
		
		g2.setColor(oldColor);
	}
	
	public void setAudioTrack(AudioTrack t){
		/* removes itself as a listener to the previous track, if any */
		if(track != null){
			track.removePropertyChangeListener(this);
		}
		/* adds itself as a listener to the new track */
		if(t != null){
			t.addPropertyChangeListener(this);
			scaleFactor = t.getScaleFactor();
		}else {
			scaleFactor = 1;
		}
		track = t;
	}
	
	public int getCursorPos() {
		Rectangle bounds = cursor.getBounds();
		return bounds.x + bounds.width/2;
	}

	public void setCursorPos(int position) {
		Rectangle bounds = cursor.getBounds();
		int currentCenterX = bounds.x + bounds.width/2;
		cursor.translate(position-currentCenterX, 0);
	}

	@Override
	public void propertyChange(PropertyChangeEvent evt) {
		switch(evt.getPropertyName()){
			case "cursorPos" : {
				setCursorPos((Integer)evt.getNewValue());
				repaint();
				break;
			}
			case "scaleFactor" : {
				scaleFactor = (Integer)evt.getNewValue();
				repaint();
				break;
			}
			case "mouseDragSelection" : {
				Selection selection = (Selection)evt.getNewValue();
				if(selection.isOpen() ){ // no loop
					loopStartMark.reset();
					loopEndMark.reset();
				}else{
					loopStartMark.reset();
					/* first point is the actual marker */
					loopStartMark.addPoint(selection.getStart(), HEIGHT-(CURSOR_HEIGHT));
					loopStartMark.addPoint(selection.getStart()+(CURSOR_WIDTH/2), HEIGHT-(CURSOR_HEIGHT*2));
					loopStartMark.addPoint(selection.getStart()+(CURSOR_WIDTH/2),HEIGHT);
					
					loopEndMark.reset();
					/* first point is the actual marker */
					loopEndMark.addPoint(selection.getEnd(),HEIGHT-(CURSOR_HEIGHT));
					loopEndMark.addPoint(selection.getEnd()-(CURSOR_WIDTH/2),HEIGHT);
					loopEndMark.addPoint(selection.getEnd()-(CURSOR_WIDTH/2),HEIGHT-(CURSOR_HEIGHT*2));
				}
				repaint();
				break;
			}
			case "preferredSize" : {
				/* resize the width acording to the new value of the listened component */
				Dimension d = (Dimension)evt.getNewValue();
				setPreferredSize(new Dimension(d.width,HEIGHT));
				break;
			}
		}
	}
	
	@Override
	public AccessibleContext getAccessibleContext(){
		if(accessibleContext == null){
			accessibleContext = new AccessibleRule();
		}
		return accessibleContext;
	}
	
	private class MouseInteraction extends MouseInputAdapter {
		private Polygon clickedMark; // either loopStartMark or loopEndMark
		int clickOffset;
		
		@Override
		public void mousePressed(MouseEvent evt){
			if(loopStartMark.contains(evt.getX(),evt.getY())){ // click on the loop start mark 
				clickedMark = loopStartMark;
				clickOffset = evt.getX() - loopStartMark.xpoints[0];
			}else if(loopEndMark.contains(evt.getX(),evt.getY())){ // click on the loop end mark
				clickedMark = loopEndMark;
				clickOffset = evt.getX() - loopEndMark.xpoints[0];
			}else if(cursor.contains(evt.getX(),evt.getY())){
				clickedMark = cursor;
				clickOffset = evt.getX() - cursor.xpoints[0];
			}
		}
		
		@Override
		public void	mouseDragged(MouseEvent evt){
			/* check again that clickedMark is != null and not reset         *
			 * things might change in the middle of the mouse interaction    *
			 * if the property change listener is called from another source */
			if(clickedMark == null)
				return;
			
			if(clickedMark != cursor && clickedMark.npoints > 0 && track != null){ // loop marks
				int mouseSelectionX = 0;
				int mouseSelectionY = 0;
				if(clickedMark == loopStartMark){ // dragging the start mark 
					if(evt.getX() > loopEndMark.xpoints[0] - 1 || (evt.getX()-clickOffset < 0) )
						return; // can't drag start mark too right, past the end mark or too left past the screen
					mouseSelectionX = evt.getX()-clickOffset;
					mouseSelectionY = loopEndMark.xpoints[0];
				}else{ // dragging the end mark 
					if(evt.getX() < loopStartMark.xpoints[0] + 1)
						return; // can't drag end mark too left, past the end mark
					mouseSelectionX = loopStartMark.xpoints[0];
					mouseSelectionY = evt.getX()-clickOffset;
				}
				scrollRectToVisible(new Rectangle(evt.getX(), evt.getY(), 1, 1));
				track.trackInteraction.setMouseSelection(mouseSelectionX, mouseSelectionY);
			} else if (clickedMark == cursor){  // cursor mark
				track.getSoundWave().scan(evt.getX());
			}
		}
		
		@Override
		public void mouseReleased(MouseEvent evt){
			if(clickedMark != null){
				if(clickedMark == cursor){
					track.getSoundWave().scan(SoundWave.STOP_SCANNING); // stop scrubbing 
				}else
					track.getSoundWave().setSelection(track.trackInteraction.getMouseSelection());
			}
			clickedMark = null;
			clickOffset = 0;
		}
		
		@Override
		public void mouseMoved(MouseEvent evt){
			if(cursor.contains(evt.getPoint())){
				setCursor(new Cursor(Cursor.E_RESIZE_CURSOR));
			}else if(loopStartMark.contains(evt.getPoint())){
				setCursor(new Cursor(Cursor.E_RESIZE_CURSOR));
			}else if(loopEndMark.contains(evt.getPoint())){
				setCursor(new Cursor(Cursor.W_RESIZE_CURSOR));
			}else{
				setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
			}
		}
	}
	
	protected class AccessibleRule extends AccessibleJComponent {
		private static final long serialVersionUID = 1L;
		
		protected AccessibleRule(){
			this.setAccessibleName("Time Bar");
			this.setAccessibleDescription( "Measures he time of tracks");
		}
		
		@Override
		public AccessibleRole getAccessibleRole(){
			return AccessibleRole.RULER;
		}
		
		@Override
		public AccessibleStateSet getAccessibleStateSet(){
			AccessibleStateSet states = super.getAccessibleStateSet();
			
			states.add(AccessibleState.HORIZONTAL);
			states.add(AccessibleState.VISIBLE);
			
			return states;
		}
	}
	
}