/* * Created on Feb 8, 2006 * * Copyright (c) 2006 P.J.Leonard * * http://www.frinika.com * * This file is part of Frinika. * * Frinika 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. * Frinika 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 Frinika; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package com.frinika.sequencer.gui; import java.awt.Color; import java.awt.Cursor; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Image; import java.awt.Point; import java.awt.Polygon; import java.awt.Rectangle; import java.awt.event.ComponentEvent; import java.awt.event.ComponentListener; import java.awt.event.MouseEvent; import java.util.ConcurrentModificationException; import javax.swing.DefaultBoundedRangeModel; import javax.swing.JPanel; import javax.swing.JToolBar; import com.frinika.project.ProjectContainer; import com.frinika.project.gui.ProjectFrame; import com.frinika.sequencer.FrinikaSequencer; import com.frinika.sequencer.SongPositionListener; import com.frinika.sequencer.model.tempo.TempoList; import com.frinika.sequencer.model.timesignature.TimeSignatureList; import com.frinika.sequencer.model.timesignature.TimeSignatureList.QStepIterator; import com.frinika.sequencer.model.timesignature.TimeSignatureList.TimeSignatureEvent; import com.frinika.sequencer.model.util.TimeUtils; import static com.frinika.localization.CurrentLocale.getMessage; /** * Basis for PianoRoll and PartView * * We imagine the pianoRoll/trackView to be drawn on a large "virtualScreen" * such that the origins (pitch=0,beat=0) coincide with (0,0) * * ItemPanel provides a view of this through itemViewRect. * * * @author pjl * */ abstract public class ItemPanel extends JPanel implements SongPositionListener, ComponentListener, Snapable { public static final int OVER_NOTHING = 0; public static final int OVER_ITEM_MIDDLE = 1; public static final int OVER_ITEM_RIGHT = 2; public static final int OVER_ITEM_LEFT = 3; protected static final int OVER_ITEM_TOP = 4; public static final int OVER_ENVELOPE_LEFT = 5; public static final int OVER_ENVELOPE_RIGHT = 6; public static final int OVER_ENVELOPE_GAIN = 7; static Cursor cursors[] = new Cursor[OVER_ENVELOPE_GAIN + 1]; static { cursors[OVER_NOTHING] = new Cursor(Cursor.DEFAULT_CURSOR); cursors[OVER_ITEM_MIDDLE] = new Cursor(Cursor.MOVE_CURSOR); cursors[OVER_ITEM_RIGHT] = new Cursor(Cursor.E_RESIZE_CURSOR); cursors[OVER_ITEM_LEFT] = new Cursor(Cursor.W_RESIZE_CURSOR); cursors[OVER_ITEM_TOP] = new Cursor(Cursor.N_RESIZE_CURSOR); cursors[OVER_ENVELOPE_LEFT] = new Cursor(Cursor.NW_RESIZE_CURSOR); cursors[OVER_ENVELOPE_RIGHT] = new Cursor(Cursor.NE_RESIZE_CURSOR); cursors[OVER_ENVELOPE_GAIN] = new Cursor(Cursor.N_RESIZE_CURSOR); } /** * */ private static final long serialVersionUID = 1L; // int cursorInc = Layout.cursorInc; // minimum jump of the tmie cursor in // pixels (increase // to reduce CPU burn) private boolean rightButtonPressed; private Graphics itemGraphics; // main offscreen image graphics private Graphics timeGraphics; // time line offscreen image graphics boolean ignoreRepaint = false; private Image itemImage; Rectangle itemViewRect = new Rectangle(); protected Rectangle lastItemViewRect = new Rectangle(); StrechyRectangle selectRect = null; protected int ticksPerBeat; // TODO yuckkkk public double userToScreen = .5; // scaling for ticks protected boolean timeBased = false; private Image timeImage; // protected int leftMargin = 0; // TODO doesn't work (well I can not work // out what to do). // I only need this to inform a rectangleZoom finish TODO a better way ? protected JToolBar toolBar; protected int xAnchor = 0; int xCursor = -1; // current time cursor position private double xFractLeftStaticWindow = .1; private double xFractRightStaticWindow = .9; protected int yAnchor = 0; StrechyRectangle zoomRect = null; protected ItemScrollPane scroller; // thing that controls the scrolling private boolean followSong = true; // automatic scrolling to keep time // cursor // in window // protected boolean quantize; // protected FrinikaSequencer sequencer; protected boolean dragArmed; // about to be dragged? but not started yet. private boolean dirty; protected int dragMode; protected ProjectContainer project; boolean hasTimeLine; private int timePanelHeight; private boolean canScrollY; protected ToolAdapter selectTool; protected ToolAdapter eraseTool; protected ToolAdapter writeTool; protected ToolAdapter dragViewTool; protected ToolAdapter rectZoomTool; /* * current tool */ protected ToolAdapter tool; /* * remember old tool doing zoom rects; */ private ToolAdapter prevTool; private ToolAdapter origTool; Polygon leftMarker; int xLMarker[] = {0, (int) (Layout.timePanelHeight * .6), 0}; int yMarker[] = {0, 0, (int) (Layout.timePanelHeight * .6)}; Polygon rightMarker; int xRMarker[] = {0, -xLMarker[1], 0}; long startLoopTime; long endLoopTime; protected Item dragItem = null; protected boolean controlIsDown = true; protected boolean altIsDown = true; ExtendingRangeModel xRangeModel = new ExtendingRangeModel(); DefaultBoundedRangeModel yRangeModel = new DefaultBoundedRangeModel(); private double endTimeOrTick = -1; TimeUtils timeUtil; protected boolean isChanging = false; //protected ProjectFrame frame; private int previousCursor; protected ItemPanel(ProjectContainer project, ItemScrollPane scroller, boolean hasTimeLine, boolean canScrollY, double ticksToScreen, boolean sampleBased) { super(false); this.userToScreen = ticksToScreen; this.timeBased = sampleBased; timeUtil = new TimeUtils(project); this.canScrollY = canScrollY; this.hasTimeLine = hasTimeLine; if (hasTimeLine) { timePanelHeight = Layout.timePanelHeight; } this.project = project; // this.frame = project; this.scroller = scroller; this.selectRect = new StrechyRectangle(this); this.zoomRect = new StrechyRectangle(this); addComponentListener(this); if (hasTimeLine) { leftMarker = new Polygon(xLMarker, yMarker, 3); rightMarker = new Polygon(xRMarker, yMarker, 3); } startLoopTime = project.getSequencer().getLoopStartPoint(); endLoopTime = project.getSequencer().getLoopEndPoint(); validateEndTick(); setToolTipText(""); // Indicate that this component has tooltips } @Override public String getToolTipText(MouseEvent event) { if (isTimeLineEvent(event)) { return getMessage("sequencer.itempanel.timeline.loopmarkers.tooltip"); } else { return null; } } public ItemScrollPane getScroller() { return scroller; } public void componentHidden(ComponentEvent arg0) { } public void componentMoved(ComponentEvent arg0) { } public synchronized void componentResized(ComponentEvent arg0) { Dimension newSize = getSize(); newSize.height = Math.max(0, newSize.height - timePanelHeight); newSize.width = Math.max(0, newSize.width); if (newSize.height == 0 || newSize.width == 0) { this.itemImage = null; this.itemGraphics = null; // System.err.println(" Warning zero height in ItemPanel image "); // return; } else { this.itemImage = createImage(newSize.width, newSize.height); this.itemGraphics = this.itemImage.getGraphics(); // this.itemViewRect.setSize(newSize); } if (newSize.width == 0) { // System.err.println(" Warning zero width in ItemPanel image "); } else { // if (hasTimeLine) { this.timeImage = createImage(newSize.width, timePanelHeight); this.timeGraphics = this.timeImage.getGraphics(); } } // newSize.width = newSize.width; this.itemViewRect.setSize(newSize); paintImages(); repaint(); // TODO move up ? } public void componentShown(ComponentEvent arg0) { } protected abstract void writePressedAt(Point p); protected abstract void writeReleasedAt(Point p); public abstract void dragTo(Point p); /** * This will be called when the mouse is released from a dragging operation * */ abstract public void endDrag(); public Rectangle getVirtualScreenRect() { return this.itemViewRect; } public abstract Item itemAt(Point p); public int mapX(int x) { return x + this.itemViewRect.x; } /** * * @param point */ public void map(Point point) { point.x += this.itemViewRect.x; point.y += this.itemViewRect.y; if (hasTimeLine) { point.y -= Layout.timePanelHeight; } } Rectangle mapRect(Rectangle screenRect) { Rectangle rect = new Rectangle(screenRect); rect.x += this.itemViewRect.x; rect.y += this.itemViewRect.y; if (hasTimeLine) { rect.y -= Layout.timePanelHeight; } return rect; } // abstract public void notifySelectionChange(); static Rectangle dirtyRect = new Rectangle(); public synchronized void notifyTickPosition(long tick) { //if (this instanceof PartView ) System.out.println("A" + tick); double userTime; if (timeBased) { userTime = project.getTempoList().getTimeAtTick(tick); } else { userTime = tick; } // make sure scroll is OK int pixPerRedraw = 1; if (sequencer.isRunning()) { if (!isShowing()) { return; } pixPerRedraw = project.getPixelsPerRedraw(); if (pixPerRedraw <= 0) { return; } } else { //if (this instanceof PartView ) System.out.println("B" ); validateEndTick(); } double scrX = userToScreen(userTime); int x = (int) (scrX / pixPerRedraw) * pixPerRedraw; //if (this instanceof PartView ) System.out.println("B " + x + " " + this.xCursor + " " + pixPerRedraw + " " + userTime + " " + scrX); long st = project.getSequencer().getLoopStartPoint(); long et = project.getSequencer().getLoopEndPoint(); if ((x == this.xCursor)) { if (!hasTimeLine) { return; } if ((st == startLoopTime) && (et == endLoopTime)) { return; } } //if (this instanceof PartView ) System.out.println("C" ); this.previousCursor = this.xCursor; this.xCursor = x; if ((st == startLoopTime) && (et == endLoopTime)) { if (followSong) { try { if (!scrollToContain(x)) { Rectangle tR = this.getBounds(); dirtyRect.y = tR.y; dirtyRect.height = tR.height; int x1 = previousCursor - this.itemViewRect.x; if (x1 >= 0 && x1 < tR.x + tR.width) { dirtyRect.x = x1; dirtyRect.width = 1; repaint(dirtyRect); } int x2 = x - this.itemViewRect.x; if (x2 >= 0 && x2 < tR.x + tR.width) { dirtyRect.x = x2; dirtyRect.width = 1; repaint(dirtyRect); } } } catch (ConcurrentModificationException e) { // TODO: This exception occurs every now and then - ignore // until a better solution is found e.printStackTrace(); } } else { repaint(); } } else { endLoopTime = et; startLoopTime = st; repaintItems(); } } public synchronized boolean scrollToContain(int x) { double posFract = (x - this.itemViewRect.x) / (double) this.itemViewRect.width; boolean jumped = false; int xLeft = 0; if (posFract < 0 || posFract > this.xFractRightStaticWindow) { xLeft = (int) (x - this.itemViewRect.width * this.xFractLeftStaticWindow); if (xLeft < 0) { xLeft = 0; } jumped = true; } if (jumped) { scroller.setX(xLeft); paintImages(); // TODO copyArea ? repaint(); } return jumped; } private synchronized void drawCursor(Graphics g, int xt) { int x = xt - this.itemViewRect.x; int y1 = 0; int h = getHeight() - 1; int y2 = y1 + h; g.setColor(Color.PINK); g.drawLine(x, y1, x, y2); // g.setPaintMode(); } public synchronized void copyImageToScreen(Graphics g) { if (this.ignoreRepaint) { return; } super.paintComponent(g); Graphics2D g2 = (Graphics2D) g; if (this.itemImage != null) { g.drawImage(this.itemImage, 0, timePanelHeight, null); } if (hasTimeLine && this.timeImage != null) { g.drawImage(this.timeImage, 0, 0, null); } if (this.zoomRect.isActive()) { g.setColor(Color.GREEN); g2.draw(this.zoomRect); } if (this.selectRect.isActive()) { g.setColor(Color.BLUE); g2.draw(this.selectRect); } } abstract protected void paintImageImpl(Rectangle clipRect, Graphics2D g); /** * paint the itemImage and timeLineImage. * * Notes:is it worth doing copyArea if we have a wide static window ? Maybe * for the user scroll ? * */ protected synchronized void paintImages() { if (this.ignoreRepaint) { return; } if (this.itemViewRect.isEmpty()) { return; } // HACK HACK HACK HACK if (true) { paintItemImage_(this.itemViewRect); if (hasTimeLine) { paintTimeImage_(this.itemViewRect.x, this.itemViewRect.width); } return; } // ************** THIS GOT COMPLICATED // ******************************************* // TODO this stuff might be worth getting working for small scrolls // (lots of) if (!this.lastItemViewRect.getSize().equals(this.itemViewRect.getSize())) { // first time or resize repaint the lot. paintItemImage_(this.itemViewRect); if (hasTimeLine) { paintTimeImage_(this.itemViewRect.x, this.itemViewRect.width); } this.lastItemViewRect.setBounds(this.itemViewRect); } else if (this.lastItemViewRect.equals(this.itemViewRect)) { System.err.println(" paintItemImage with same virtual rect !!!!!!! "); // Same as last rect probably mistake in my logic / resize causes // this to happen. // paint it anyway otherwise it doesn't work paintItemImage_(this.itemViewRect); if (hasTimeLine) { paintTimeImage_(this.itemViewRect.x, this.itemViewRect.width); } } else if (this.lastItemViewRect.x != this.itemViewRect.x) { if (this.lastItemViewRect.y != this.itemViewRect.y) { paintItemImage_(this.itemViewRect); if (hasTimeLine) { paintTimeImage_(this.itemViewRect.x, this.itemViewRect.width); } this.lastItemViewRect.setBounds(this.itemViewRect); } else { if (hasTimeLine) { paintTimeImage_(this.itemViewRect.x, this.itemViewRect.width); } // TODO scrolly stuff ? scrollItemImageX(); this.lastItemViewRect.setBounds(this.itemViewRect); } } else if (this.lastItemViewRect.y != this.itemViewRect.y) { scrollItemImageY(); this.lastItemViewRect.setBounds(this.itemViewRect); } } private synchronized void paintItemImage_(Rectangle visibleRect) { this.itemGraphics.translate(-this.itemViewRect.x, -this.itemViewRect.y); paintImageImpl(visibleRect, (Graphics2D) this.itemGraphics); this.itemGraphics.translate(this.itemViewRect.x, this.itemViewRect.y); paintImageImplLabel((Graphics2D) this.itemGraphics); } /** * override this to draw on top of screen * * @param graphics */ protected abstract void paintImageImplLabel(Graphics2D graphics); private void paintTimeImage_(int x, int width) { assert (hasTimeLine); this.timeGraphics.translate(-this.itemViewRect.x, 0); paintTimeImpl(x, width, (Graphics2D) this.timeGraphics); this.timeGraphics.translate(this.itemViewRect.x, 0); } public void paintTimeImpl(int xClip, int widthClip, Graphics g1) { if (timeBased) { paintTimeImplUser(xClip, widthClip, g1); } else { paintTimeImplTick(xClip, widthClip, g1); } } private void paintTimeImplTick(int xClip, int widthClip, Graphics g1) { assert (hasTimeLine); Graphics2D g = (Graphics2D) g1; // TODO clipping rect g.setColor(Color.BLACK); /* * TODO This next bit needs thinking about. because the text has a width * we must sometimes draw text even if the start point is outside the * graphics cip region this is a brute force implementation. */ Rectangle b = new Rectangle(); b.x = this.itemViewRect.x; b.y = 0; b.height = timePanelHeight; b.width = this.itemViewRect.width; g.fill(b); // if (true) return; // Add a bit to the clipping because of extent of the characters (TODO // do this better) int w = b.width + 400; int x = Math.max(0, b.x - 200); int y = b.y; int h = b.height; int charHeight = timePanelHeight - 5; int y1 = Math.max(y, charHeight); double step = this.getSnapQuantization() / ticksPerBeat; TimeSignatureList ts = project.getTimeSignatureList(); assert (x >= 0); double beat1 = screenToTickAbs(x, true) / ticksPerBeat; double beat2 = screenToTickAbs(x + w, true) / ticksPerBeat; QStepIterator iter = project.getTimeSignatureList().createQStepIterator(beat1, beat2, step); g.setColor(Color.WHITE); boolean drawlines = (y1 < y + h); boolean drawNumber = (y < charHeight); boolean drawSub = drawlines && userToScreen((long) (step * ticksPerBeat)) > 5; boolean drawBeat = drawlines && userToScreen((long) (ticksPerBeat)) > 5; int minSpace = 50; int xlast = -2 * minSpace; while (iter.hasNext()) { iter.next(); double beat = iter.getBeat(); boolean isBar = iter.isBar(); int x1 = (int) (userToScreen((long) (beat * ticksPerBeat))); if (isBar) { if (x1 - xlast > minSpace) { g.setColor(Color.WHITE); g.drawString(String.valueOf(iter.getBar()), x1, charHeight - 2); xlast = x1; g.drawLine(x1, y1, x1, y1 + 3); } } } long xStart = (int) userToScreen(startLoopTime); long xEnd = (int) userToScreen(endLoopTime); if (xStart > x && xStart < x + w) { g.setColor(Color.YELLOW); g.translate(xStart, 0); g.fill(leftMarker); g.translate(-xStart, 0); } if (xEnd > x && xEnd < x + w) { g.setColor(Color.YELLOW); g.translate(xEnd, 0); g.fill(rightMarker); g.translate(-xEnd, 0); } } public void paintTimeImplUser(int xClip, int widthClip, Graphics g1) { assert (hasTimeLine); Graphics2D g = (Graphics2D) g1; // TODO clipping rect g.setColor(Color.BLACK); /* * TODO This next bit needs thinking about. because the text has a width * we must sometimes draw text even if the start point is outside the * graphics cip region this is a brute force implementation. */ Rectangle b = new Rectangle(); b.x = this.itemViewRect.x; b.y = 0; b.height = timePanelHeight; b.width = this.itemViewRect.width; g.fill(b); // if (true) return; // Add a bit to the clipping because of extent of the characters (TODO // do this better) int w = b.width + 400; int x = Math.max(0, b.x - 200); int y = b.y; int h = b.height; int charHeight = timePanelHeight - 5; int y1 = Math.max(y, charHeight); double step = this.getSnapQuantization() / ticksPerBeat; TimeSignatureList ts = project.getTimeSignatureList(); TempoList tl = project.getTempoList(); assert (x >= 0); double beat1 = screenToTickAbs(x, true) / ticksPerBeat; double beat2 = screenToTickAbs(x + w, true) / ticksPerBeat; QStepIterator iter = project.getTimeSignatureList().createQStepIterator(beat1, beat2, step); g.setColor(Color.WHITE); boolean drawlines = (y1 < y + h); boolean drawNumber = (y < charHeight); boolean drawSub = true; //drawlines // && userToScreen((long) (step * ticksPerBeat)) > 5; boolean drawBeat = true; // drawlines && userToScreen((long) (ticksPerBeat)) > 5; int minSpace = 50; int xlast = -2 * minSpace; while (iter.hasNext()) { iter.next(); double beat = iter.getBeat(); boolean isBar = iter.isBar(); int x1 = (int) (userToScreen(tl.getTimeAtTick((beat * ticksPerBeat)))); if (isBar) { if (x1 - xlast > minSpace) { g.setColor(Color.WHITE); g.drawString(String.valueOf(iter.getBar()), x1, charHeight - 2); xlast = x1; g.drawLine(x1, y1, x1, y1 + 3); } } } long xStart = (int) userToScreen(tl.getTimeAtTick(startLoopTime)); long xEnd = (int) userToScreen(tl.getTimeAtTick(endLoopTime)); if (xStart > x && xStart < x + w) { g.setColor(Color.YELLOW); g.translate(xStart, 0); g.fill(leftMarker); g.translate(-xStart, 0); } if (xEnd > x && xEnd < x + w) { g.setColor(Color.YELLOW); g.translate(xEnd, 0); g.fill(rightMarker); g.translate(-xEnd, 0); } } /** * This is called when the scale of painoRoll to screen mapping changes the * sub class must workpout the new postion of all items. */ public synchronized void scaleX(double fact) { // We want left to stay still TODO do we really ? // Current visible rect Rectangle vr = this.itemViewRect; this.userToScreen = this.userToScreen * fact; vr.x = (int) (vr.x * fact); this.xCursor = (int) (sequencer.getTickPosition() * userToScreen); rebuildXScrollBars(); repaintItems(); } /** * Convert virtual screen x to a tick. with optional quantization * * Do not use this if x is a delta quantity * * @param x * @return */ public long screenToTickAbs(int x, boolean quantizeMe) { return screenToTickAbs(x,quantizeMe,false); } /** * Convert virtual screen x to a tick. with optional quantization * * if (drumMode) quantize using round not truncate * Do not use this if x is a delta quantity * * @param x * @return */ public long screenToTickAbs(int x, boolean quantizeMe,boolean drumMode) { double tt = (x / this.userToScreen); // if sample based then we need if (timeBased) { tt = project.getTempoList().getTickAtTime(tt); } if (quantizeMe) { double quant = this.getSnapQuantization(); if (quant > 0.0) { // tt = (long) (Math.round(tt / this.getSnapQuantization())) // * this.getSnapQuantization(); if (!drumMode) { tt = (long) (tt / quant) * quant; } else { tt = (long) ((tt+0.5*quant) / quant) * quant; } } else { double beat = tt / project.getTicksPerBeat(); TimeSignatureEvent ev = project.getTimeSignatureList().getEventAtBeat((int) beat); int nBar = (int) ((beat - ev.beat + ev.beatsPerBar / 2.0) / ev.beatsPerBar); tt = (ev.beat + nBar * ev.beatsPerBar) * project.getTicksPerBeat(); // System.out.println(" STT -ve quant " + tt); } } return (long) tt; } /** * Convert delta on the virtual screen to a tick with optional quntization * * @param x * reference point to deduce bar boundaries * @param dx * screen delta to convert * @return */ public long screenToTickRel(int x, int dx, boolean quantizeMe) { assert (!timeBased); double tt = (dx / this.userToScreen); // XXX if (quantizeMe) { double quant = this.getSnapQuantization(); if (quant > 0.0) { tt = (long) (Math.round(tt / this.getSnapQuantization())) * this.getSnapQuantization(); } else { double beat = x / this.userToScreen / project.getTicksPerBeat(); TimeSignatureEvent ev = project.getTimeSignatureList().getEventAtBeat((int) beat); quant = ev.beatsPerBar * project.getTicksPerBeat(); tt = (long) (Math.round(tt / quant)) * quant; } } return (long) tt; } abstract public double getSnapQuantization(); abstract public boolean isSnapQuantized(); // final protected long snaptoQuantize(double val) { // return (long) ((Math.round(val / this.getSnapQuantization())) * this // .getSnapQuantization()); // } protected synchronized void scrollItemImageX() { int dx = this.itemViewRect.x - this.lastItemViewRect.x; // if scroll has jumped to far might just piant the lot. if (Math.abs(dx) > this.lastItemViewRect.width) { dirty = true; repaintItems(); // paintItemImage_(this.itemViewRect); return; } Rectangle exposedRect = new Rectangle(this.itemViewRect); if (dx > 0) { this.itemGraphics.copyArea(dx, 0, this.itemViewRect.width - dx - 1, this.itemViewRect.height, -dx, 0); } else { this.itemGraphics.copyArea(0, 0, this.itemViewRect.width + dx - 1, this.itemViewRect.height, -dx, 0); } if (dx > 0) { int xOld = exposedRect.x; exposedRect.x = this.lastItemViewRect.x + this.lastItemViewRect.width - 1; exposedRect.width -= exposedRect.x - xOld; } else { exposedRect.width = -dx; } paintItemImage_(exposedRect); if (hasTimeLine) { paintTimeImage_(exposedRect.x, exposedRect.width); } } protected synchronized void scrollItemImageY() { int dy = this.itemViewRect.y - this.lastItemViewRect.y; if (Math.abs(dy) > this.lastItemViewRect.height) { paintItemImage_(this.itemViewRect); return; } Rectangle exposedRect = new Rectangle(this.itemViewRect); if (dy > 0) { this.itemGraphics.copyArea(0, dy, this.itemViewRect.width, this.itemViewRect.height - dy - 1, 0, -dy); } else { this.itemGraphics.copyArea(0, 0, this.itemViewRect.width, this.itemViewRect.height + dy - 1, 0, -dy); } if (dy > 0) { int yOld = exposedRect.y; exposedRect.y = this.lastItemViewRect.y + this.lastItemViewRect.height - 1; exposedRect.height -= exposedRect.y - yOld; } else { exposedRect.height = -dy; } paintItemImage_(exposedRect); } @Override public void scrollRectToVisible(Rectangle r) { System.err.println("scrollToVisibleRect not allow for ItemPanel"); } public Point scrollToContian(Point p) { int dx = p.x - (itemViewRect.x + itemViewRect.width); if (dx > 0) { if (xRangeModel.getMaximum() < p.x) { xRangeModel.setMaximum(p.x); } xRangeModel.setValue(itemViewRect.x + dx - 1); } else if (p.x > 0) { dx = p.x - itemViewRect.x; if (dx < 0) { xRangeModel.setValue(itemViewRect.x + dx + 1); } else { dx = 0; } } else { dx = 0; } int dy = p.y - (itemViewRect.y + itemViewRect.height); if (dy > 0) { if (p.y < yRangeModel.getMaximum()) { yRangeModel.setValue(itemViewRect.y + dy - 1); } else { dy = 0; } } else if (p.y > 0) { dy = p.y - itemViewRect.y; if (dy < 0) { yRangeModel.setValue(itemViewRect.y + dy + 1); } else { dy = 0; } } else { dy = 0; } long endTimeOrTick2 = (long) ((itemViewRect.x + itemViewRect.width) / userToScreen); if (endTimeOrTick2 > endTimeOrTick) { endTimeOrTick = endTimeOrTick2; } return new Point(dx, dy); } /** * Clears all items from the ItemPanels selection * */ public abstract void clientClearSelection(); /** * Add items in the rectangle to the ItemPanels selection. */ public abstract void selectInRect(Rectangle rect, boolean shift); public void setIgnoreRepaints(boolean yes) { this.ignoreRepaint = yes; } /** * Sets the x cordinate of the viewport on the items space. * * @param xNew */ public synchronized void setX(int xNew) { if (isChanging) { return; } if (xNew == this.itemViewRect.x) { return; } this.itemViewRect.x = xNew; paintImages(); repaint(); // TODO move up ? } public synchronized void setY(int yNew) { if (isChanging) { return; } if (!canScrollY) { return; } if (yNew == this.itemViewRect.y) { return; } this.itemViewRect.y = yNew; paintImages(); repaint(); } /** * Convert time or tick to virtual screen x * * @param userTIme * @return */ public double userToScreen(double userTIme) { return (userTIme * this.userToScreen); } public void zoomIn() { scaleX(1.0 / 0.8); } public void zoomOut() { scaleX(0.8); } public void zoomToRect(Rectangle rect) { if (rect.width <= 0) { return; } Rectangle vr = this.itemViewRect; double scale = vr.width / (double) rect.width; double tt = this.userToScreen * scale; if (tt > 5) { tt = 5; } this.userToScreen = tt; vr.x = (int) (rect.x * scale); rebuildXScrollBars(); dirty = true; repaintItems(); } protected void rebuildXScrollBars() { double endTime2 = project.getEndTick(); if (timeBased) { endTime2 = project.getTempoList().getTimeAtTick(endTime2); } endTime2 = endTime2 * userToScreen; if (scroller.horizScroll.getMaximum() - scroller.horizScroll.getVisibleAmount() < endTime2) { scroller.horizScroll.setMaximum((int) endTime2 + itemViewRect.width); } scroller.horizScroll.setVisibleAmount(itemViewRect.width); scroller.horizScroll.setValue(itemViewRect.x); } public void setRightButton(boolean buttonState) { this.rightButtonPressed = buttonState; } /** * @return Returns the rightButtonPressed. */ public boolean isRightButtonPressed() { return rightButtonPressed; } /** * * @param b * if true panel will auto scroll to follow sequencer * songPosition. */ public void followSong(boolean b) { followSong = b; if (b) { scrollToContain(xCursor); } } public boolean isFollowSong() { return followSong; } public abstract void setTimeAtX(int x); public boolean requiresNotificationOnEachTick() { return false; } public boolean pointInTimeLine(int y) { if (!hasTimeLine) { return false; } return y <= timePanelHeight; } public void setToolBar(ItemRollToolBar bar) { this.toolBar = bar; } public void armDrag(Point p, Item item) { dragArmed = true; dragItem = item; xAnchor = p.x; yAnchor = p.y; } public abstract void rightButtonPressedOnItem(int x, int y); // TODO common interface for the selection containers ? public abstract void clientAddToSelection(Item item); public abstract void clientRemoveFromSelection(Item item); public abstract void erase(Item item); /** * flag reconstruction the image of the notes and request a repaint * */ public void repaintItems() { // TODO area of dirty ? dirty = true; repaint(); } public void paintComponent(Graphics g) { // System.out.println(g.getClip()); if (dirty) { // System.out.println("DIRTY"); paintImages(); dirty = false; } copyImageToScreen(g); drawCursor(g, this.xCursor); } public void rectZoomFinished() { ((ItemRollToolBar) toolBar).rectZoomFinished(); } public abstract int getHoverStateAt(Point p); public void setDragMode(int mode) { this.dragMode = mode; setCursor(cursors[mode]); } public ProjectContainer getProjectContainer() { return project; } public void setTool(String name) { tool = getTool(name); if (tool == prevTool) { return; } removeMouseListener(prevTool); removeMouseMotionListener(prevTool); if (tool == null) { return; } addMouseListener(tool); addMouseMotionListener(tool); setCursor(tool.getCursor()); if (prevTool instanceof EditTool) { origTool = prevTool; } prevTool = tool; } /** * Get the tool associated with name. * * @param name * @return */ public ToolAdapter getTool(String name) { if (name.equals("origtool")) { return origTool; } else if (name.equals("select")) { return selectTool; } else if (name.equals("erase")) { return eraseTool; } else if (name.equals("write")) { return writeTool; } else if (name.equals("magrect")) { return rectZoomTool; } else if (name.equals("dragview")) { return dragViewTool; } else if (name.equals("dragview")) { return dragViewTool; } else { try { throw new Exception(" Unknown tool " + name); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } // TODO Auto-generated method stub return null; } /** * Notififiction that mouse has been drag during after a writeTool press. * Default to ignoring it * * @param p * */ public void writeDraggedAt(Point p) { } abstract public void setSnapQuantization(double quant); /* * Used by slection tool to provide audio feed back default to do nothing. * */ public void feedBack(Item item) { } boolean isTimeLineEvent(MouseEvent e) { if (pointInTimeLine(e.getY())) { int x = mapX(e.getX()); if (e.getButton() == MouseEvent.BUTTON1) { if (e.isAltDown() || (e.isShiftDown() && e.isControlDown())) // Jens, // Alt-click // might // not // be // available // on // Linux // (seems // to // be // taken // by // the // window-manager), // so // also // allow // Shift-Ctrl { setRightMarkAt(x); } else if (e.isControlDown()) { setLeftMarkAt(x); } else { setTimeAtX(x); } } return true; } return false; } private void setRightMarkAt(int x) { long tick = screenToTickAbs(x, isSnapQuantized()); sequencer.setLoopEndPoint(tick); } private void setLeftMarkAt(int x) { long tick = screenToTickAbs(x, isSnapQuantized()); sequencer.setLoopStartPoint(tick); } public abstract void setSnapQuantized(boolean b); /** * method to let the ItemPanel know the state of the control key Look at * controlIsDown field. */ public void setControlState(boolean b) { controlIsDown = b; } /** * Force a complete redraw on next repaint(); */ public void setDirty() { dirty = true; } void validateEndTick() { double endTimeOrTick2 = project.getEndTick(); // System.out.println(" project end time is "+ project.getTimeUtils().tickToBarBeatTick((long) endTimeOrTick2)); if (timeBased) { endTimeOrTick2 = project.getTempoList().getTimeAtTick(endTimeOrTick2); } xRangeModel.setExtent(itemViewRect.width); xRangeModel.setMaximum((int) (endTimeOrTick2 * userToScreen)); endTimeOrTick = endTimeOrTick2; } public abstract void setFocus(Item item); public void ignorePartWarp(boolean b) { // TODO Auto-generated method stub } public abstract void clientNotifySelectionChange(); public ExtendingRangeModel getXRangeModel() { return xRangeModel; } public DefaultBoundedRangeModel getYRangeModel() { return yRangeModel; } public void setAltState(boolean b) { altIsDown = b; } // public void rightButtonPressedInSpace() { // // TODO Auto-generated method stub // // } }