/*
* Jacareto Copyright (c) 2002-2005
* Applied Computer Science Research Group, Darmstadt University of
* Technology, Institute of Mathematics & Computer Science,
* Ludwigsburg University of Education, and Computer Based
* Learning Research Group, Aachen University. All rights reserved.
*
* Jacareto 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.
*
* Jacareto 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 Jacareto; if not, write to the Free
* Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
*/
package jacareto.trackimpl;
import jacareto.track.TrackModel;
import jacareto.track.block.Block;
import jacareto.track.block.BlockType;
import org.apache.commons.lang.Validate;
import java.awt.Point;
import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
/**
* <p>
* Helper class which calculates coordinates for the {@link TrackViewPanel} from data of the
* TrackModel.
* </p>
*
* <p>
* The {@link TrackModel} has to be set and the ms per pixel have to be calculated before the
* coordinates can be calculated.
* </p>
*
* @author Oliver Specht
* @version $revision$
*/
public class ViewPanelHelper {
/** The factor the y position of the blocks will be calculated from (~ % of the height) */
private static final double Y_POSITION_FACTOR_AUDIO = 0.65;
private static final double Y_POSITION_FACTOR_VIDEO = 0.15;
/** The factor, the height of the blocks will be calculated from (~ % of the height) */
private static final double HEIGHT_FACTOR_AUDIO = 0.2;
private static final double HEIGHT_FACTOR_VIDEO = 0.2;
/** The current heights and positions of the blocks */
private int yPositionAudio;
private int yPositionVideo;
private int heightAudio;
private int heightVideo;
/** The TrackModel this Helper works with */
TrackModel trackModel;
/** The number of ms one pixel has to be interpreted */
private double msPerPixel = 0.0;
/** The width of the viewPanel */
private int viewPanelWidth = 0;
/** The height of the viewPanel */
private int viewPanelHeight = 0;
/** the block which has been marked by a mouse click */
private Block markedBlock = null;
/** The visible blocks */
List visibleBlocks = new ArrayList();
/** The time where the mouse pointer was when the left mouse button was clicked */
private long mousePointerTimeOffset = 0;
/**
* Sets the width and height of the TrackViewPanel
*
* @param width width of the view panel
* @param height height of the view panel
*/
public void setViewPanelSize (int width, int height) {
this.viewPanelWidth = width;
this.viewPanelHeight = height;
this.calculateMsPerPixel ();
this.calculateBlockPositions ();
}
/**
* Returns true if block overlaps any block of the same type in the same track (~BlockType)
*
* @param toCheck block to check for overlapping
* @param newStartTime new start time to be set for the block and to be checked
*
* @return boolean
*/
public boolean isOverlapping (Block toCheck, long newStartTime) {
Validate.notNull (toCheck);
if (((newStartTime + toCheck.getDuration ()) > this.trackModel.getEndTime ()) ||
(newStartTime < 0)) {
return true;
}
List checkList = this.trackModel.getBlocksInRange (toCheck.getType (), newStartTime,
newStartTime + toCheck.getDuration (), false);
// more than one => overlapping
if (checkList.size () > 1) {
return true;
} else if (checkList.size () == 1) {
// only one => check if it is the block itself
if (! checkList.get (0).equals (toCheck)) {
return true;
}
}
return false;
}
/**
* Calculate the position and height of the blocks depending on the size of the TrackViewPanel.
*/
private void calculateBlockPositions () {
this.heightAudio = (int) (this.viewPanelHeight * ViewPanelHelper.HEIGHT_FACTOR_AUDIO);
this.heightVideo = (int) (this.viewPanelHeight * ViewPanelHelper.HEIGHT_FACTOR_VIDEO);
this.yPositionAudio = (int) (this.viewPanelHeight * ViewPanelHelper.Y_POSITION_FACTOR_AUDIO);
this.yPositionVideo = (int) (this.viewPanelHeight * ViewPanelHelper.Y_POSITION_FACTOR_VIDEO);
}
/**
* Reruns the current Y position for the audio bar
*
* @return int
*/
public int getYPositionAudio () {
return this.yPositionAudio;
}
/**
* Reruns the current Y position for the video bar
*
* @return int
*/
public int getYPositionVideo () {
return this.yPositionVideo;
}
/**
* Reruns the current height of the audio bar
*
* @return int
*/
public int getHeightAudio () {
return this.heightAudio;
}
/**
* Reruns the current height of the video bar
*
* @return int
*/
public int getHeightVideo () {
return this.heightVideo;
}
/**
* Calculates the ms per Pixel
*/
private void calculateMsPerPixel () {
if (this.trackModel != null) {
this.msPerPixel = ((double) this.trackModel.getEndTime () / (double) this.viewPanelWidth);
}
}
/**
* Sets a new {@link TrackModel} for this ViewPanelHelper.
*
* @param trackModel {@link TrackModel}
*/
public void setModel (TrackModel trackModel) {
this.trackModel = trackModel;
this.clearVisibleBlocks ();
// Add audio and video blocks
this.visibleBlocks = this.trackModel.getBlocksInRange (BlockType.AUDIO, 0,
this.trackModel.getEndTime (), false);
this.visibleBlocks.addAll (this.trackModel.getBlocksInRange (BlockType.VIDEO, 0,
this.trackModel.getEndTime (), false));
}
/**
* <p>
* Returns a rectangle which visualizes the given block.
* </p>
*
* @param block {@link Block}
*
* @return {@link Rectangle}
*/
public Rectangle getRectangle (Block block) {
int x = (int) (this.trackModel.getStartTime (block) / this.msPerPixel);
int width;
if (this.trackModel.isEndAligned (block)) {
long alignedToBlockStartTime = this.trackModel.getStartTime (this.trackModel.getAlignedToBlock (
block));
width = (int) ((alignedToBlockStartTime / this.msPerPixel) - x);
} else {
width = (int) (block.getDuration () / this.msPerPixel);
}
if (block.getType ().equals (BlockType.AUDIO)) {
return new Rectangle(x, this.yPositionAudio, width, this.heightAudio);
} else if (block.getType ().equals (BlockType.VIDEO)) {
return new Rectangle(x, this.yPositionVideo, width, this.heightVideo);
}
return null;
}
/**
* Returns the block the user has clicked on or null if he didn't click in a block.
*
* @param point Point
*
* @return {@link Block} where the user clicked on
*/
public Block getClickedBlock (Point point) {
Iterator iter = this.visibleBlocks.iterator ();
while (iter.hasNext ()) {
Block block = (Block) iter.next ();
Rectangle rect = getRectangle (block);
if (rect.contains (point)) {
return block;
}
}
return null;
}
/**
* <p>
* Returns true if the wantedStartTime is less than zero or if the wantedStartTime + the
* duration of the block is greater than the end time of the track model.
* </p>
*
* @param block the {@link Block} to be checked
* @param wantedStartTime the wanted start time
*
* @return boolean
*/
public boolean isOutOfTrackModelTimes (Block block, long wantedStartTime) {
if (wantedStartTime < 0) {
return true;
} else if ((wantedStartTime + block.getDuration ()) > this.trackModel.getEndTime ()) {
return true;
}
return false;
}
/**
* Returns the next possible start time for the given block. Next possible means that the block
* will be shifted to the "left", so the start time will be reduced.
*
* @param block Block for which the next possible start time will be calculated
* @param wantedStartTime the start time which should be set
*
* @return long new start time or -1 if no start time could be determined
*/
public long getNextPossibleStartTime (Block block, long wantedStartTime) {
long blockLength = this.trackModel.getStartTime (block) + block.getDuration ();
// get all blocks which lie before the given block (ordered by start time)
List previousBlocks = this.trackModel.getBlocksInRange (block.getType (), 0,
wantedStartTime, false);
// reverse the block list
Collections.reverse (previousBlocks);
Iterator iter = previousBlocks.iterator ();
while (iter.hasNext ()) {
Block currentBlock = (Block) iter.next ();
long currentBlockEndTime = this.trackModel.getStartTime (currentBlock) +
currentBlock.getDuration ();
long nextBlockStartTime = this.getNextBlockStartTime (block.getType (),
currentBlockEndTime);
if ((nextBlockStartTime - currentBlockEndTime) < blockLength) {
return nextBlockStartTime - blockLength;
}
}
return -1;
}
/**
* Returns the time span to the next block or the time to the end of the track
*
* @param type BlockType for which the search should be performed
* @param time time when the searching should start
*
* @return long
*/
private long getNextBlockStartTime (BlockType type, long time) {
long trackModelEndTime = this.trackModel.getEndTime ();
Validate.isTrue (time < trackModelEndTime);
Validate.isTrue (type.equals (BlockType.AUDIO) || type.equals (BlockType.VIDEO));
List blockList = this.trackModel.getBlocksInRange (type, time, trackModelEndTime, false);
if (blockList.size () > 0) {
Block block = (Block) blockList.get (0);
return this.trackModel.getStartTime (block);
}
return -1;
}
/**
* Returns all the visible blocks.
*
* @return {@link List} of {@link Block blocks}
*/
public List getVisibleBlocks () {
return this.visibleBlocks;
}
/**
* Returns the Block the user pressed the mouse button down or null if there was no block
*
* @param point Point where the user pressed the mouse down
*
* @return Block
*/
public Block getMousePressedBlock (Point point) {
Iterator iter = this.visibleBlocks.iterator ();
while (iter.hasNext ()) {
Block block = (Block) iter.next ();
Rectangle rect = getRectangle (block);
// block has been selected to drag and drop
if (rect.contains (point)) {
this.mousePointerTimeOffset = this.getTime (point) -
this.trackModel.getStartTime (block);
return block;
}
}
return null;
}
/**
* <p>
* Save the position where the user has clicked on to provide correct drag and drop.
* </p>
*
* @param point {@link Point} the user has clicked to
* @param block {@link Block} the user has clicked in
*/
public void setMousePointerTimeOffset (Point point, Block block) {
this.mousePointerTimeOffset = this.getTime (point) - this.trackModel.getStartTime (block);
}
/**
* Returns the mousePointerTimeOffset for the {@link TrackViewPanel} to move the blocks
* correctly.
*
* @return long mousePointerTimeOffset
*/
public long getMousePointerTimeOffset () {
return this.mousePointerTimeOffset;
}
/**
* <p>
* Returns the {@link BlockType} of the track where the user has clicked into.
* </p>
*
* @param point {@link Point}
*
* @return {@link BlockType}
*/
public BlockType getClickedTrack (Point point) {
Rectangle rectAudio = new Rectangle(0, this.getYPositionAudio (), this.viewPanelWidth,
this.getHeightAudio ());
Rectangle rectVideo = new Rectangle(0, this.getYPositionVideo (), this.viewPanelWidth,
this.getHeightVideo ());
if (rectAudio.contains (point)) {
return BlockType.AUDIO;
} else if (rectVideo.contains (point)) {
return BlockType.VIDEO;
}
return null;
}
/**
* <p>
* Returns the time the given {@link Point} represents depending on the msPerPixel.
* </p>
*
* @param point point the (start-) time should be calculated for
*
* @return long the (new start-) time in ms
*/
public long getTime (Point point) {
long returnValue = (long) (point.getX () * this.msPerPixel);
int blockWidth = 0;
if (this.trackModel.isEndAligned (this.markedBlock)) {
blockWidth = (int) ((this.trackModel.getStartTime (this.markedBlock) +
this.trackModel.getStartTime (this.trackModel.getAlignedToBlock (this.markedBlock))) / this.msPerPixel);
} else {
blockWidth = (int) ((this.trackModel.getStartTime (this.markedBlock) +
this.markedBlock.getDuration ()) / this.msPerPixel);
}
if ((point.getX () + blockWidth) > this.viewPanelWidth) {
// TODO: Abchecken!!! Da muss noch berlegt werden, da ein Block evtl.
// aligned ist und solange er verschoben wird, muss das Alignment
// aufgehoben werden oder nicht angezeigt
}
return returnValue;
}
/**
* Clears the visible blocks so the newly visible blocks can be calculated
*/
public void clearVisibleBlocks () {
this.visibleBlocks.clear ();
}
/**
* True, if the given point is contained in a block
*
* @param point {@link Point} where the event occured
*
* @return boolean
*/
public boolean isPointInBlock (Point point) {
Iterator iter = this.visibleBlocks.iterator ();
while (iter.hasNext ()) {
Block block = (Block) iter.next ();
Rectangle rect = getRectangle (block);
if (rect.contains (point)) {
return true;
}
}
return false;
}
/**
* Returns the block which contains the point
*
* @param point
*
* @return Block
*/
public Block getBlock (Point point) {
Iterator iter = this.visibleBlocks.iterator ();
while (iter.hasNext ()) {
Block block = (Block) iter.next ();
if (getRectangle (block).contains (point)) {
return block;
}
}
return null;
}
/**
* Returns the {@link Block} whch is currently marked (~mouse over)
*
* @return Block {@link Block}
*/
public Block getMarkedBlock () {
return this.markedBlock;
}
/**
* Marks the block so he is drawn in another color.
*
* @param block
*/
public void markBlock (Block block) {
this.markedBlock = block;
}
/**
* Unmarks the markedBlock field
*/
public void unmarkBlock () {
this.markedBlock = null;
}
/**
* True, if the given block equals the markedBlock
*
* @param block
*
* @return true, if given {@link Block} is marked
*/
public boolean isBlockMarked (Block block) {
if ((this.markedBlock != null) && (block.equals (this.markedBlock))) {
return true;
}
return false;
}
}
|