/* * SmpMapPanel.java * (FScape) * * Copyright (c) 2001-2016 Hanns Holger Rutz. All rights reserved. * * This software is published under the GNU General Public License v3+ * * * For further information, please contact Hanns Holger Rutz at * contact@sciss.de */ package de.sciss.fscape.gui; import de.sciss.fscape.session.ModulePanel; import de.sciss.fscape.util.Constants; import de.sciss.fscape.util.DoublePoint; import de.sciss.fscape.util.Param; import de.sciss.fscape.util.ParamSpace; import de.sciss.fscape.util.SmpMap; import de.sciss.fscape.util.SmpZone; import de.sciss.gui.GUIUtil; import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ComponentEvent; import java.awt.event.ComponentListener; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.io.File; import java.util.NoSuchElementException; import java.util.Vector; /** * Utility class for the SmpSynDlg allowing * the placement of sound regions in a * frequency/dynamic plane. */ public class SmpMapPanel extends JPanel implements ComponentListener, MouseListener, MouseMotionListener { // -------- public variables -------- /** * ActionCommands, die ein registrierter (AddActionListener()) * ActionListener empfaengt. Angefuegt wird die uniqueID der * correspondierden SmpZone */ public static final String ACTION_BOXSELECTED = "act"; public static final String ACTION_BOXDESELECTED = "des"; public static final String ACTION_BOXCHANGED = "mov"; public static final String ACTION_BOXCREATED = "new"; public static final String ACTION_BOXDELETED = "rem"; public static final String ACTION_SPACECHANGED = "spc"; // -------- private variables -------- // gerade angewaehlte Box; wenn keiner ausgewaehlt, muss // er auf dummyBox gesetzt werden, damit keine Null-Pointer Fehler // auftreten koennen! protected SmpBox currentSmpBox; protected SmpBox dummyBox; private Cursor lastCursor; // Cursor previously used to dnd or edit-op // this is all for drag+drop private SmpBox dragSource = null; private boolean dragState = false; // true for drag+drop private int dragType = DRAG_NONE; private int dragOriginX; // last coordinates private int dragOriginY; // last coordinates private Rectangle dragBounds = null; private Rectangle dragRubber; private SmpZone dragSmp; private DoublePoint dragTopLeft; // Space-Koordinaten private DoublePoint dragBottomRight; // folgende Werte ODER-verknuepft private static final int DRAG_NONE = 0x00; // no drag private static final int DRAG_TOP = 0x01; // drag top margin private static final int DRAG_LEFT = 0x02; // drag left margin private static final int DRAG_BOTTOM= 0x04; // drag bottom margin private static final int DRAG_RIGHT = 0x08; // drag right margin private static final int DRAG_MOVE = 0x0F; // (drag all four margins) private int cursorID[] = { Cursor.DEFAULT_CURSOR, Cursor.N_RESIZE_CURSOR, Cursor.W_RESIZE_CURSOR, Cursor.NW_RESIZE_CURSOR, Cursor.S_RESIZE_CURSOR, Cursor.DEFAULT_CURSOR, Cursor.SW_RESIZE_CURSOR, Cursor.DEFAULT_CURSOR, Cursor.E_RESIZE_CURSOR, Cursor.NE_RESIZE_CURSOR, Cursor.DEFAULT_CURSOR, Cursor.DEFAULT_CURSOR, Cursor.SE_RESIZE_CURSOR, Cursor.DEFAULT_CURSOR, Cursor.DEFAULT_CURSOR, Cursor.MOVE_CURSOR }; // fuer SmpBox protected static final int SB_STATE_NORMAL = 0; protected static final int SB_STATE_SELECTED = 3; protected static final int SB_STATE_UNKNOWN = -1; protected SmpMap smpMap; protected Vector<SmpBox> smpBoxes; // !selbe Indices wie smpMap protected Dimension dim; // Panel-Size; vom Component-Listener geupdated protected double vSpaceLog; // ln( vSpace.max / vSpace.min) fuer logarithmische Skala wichtig // dieser Button wird nicht angezeigt, sondern dient nur als // Institution zum Verwalten der ActionListener und fuer // den Event-Dispatch! private Button actionComponent; // Fehlermeldungen protected static final String ERR_CORRUPTED = "Internal data corrupted. Please report bug!"; // -------- public methods -------- public SmpMapPanel() { super(); actionComponent = new Button(); dummyBox = new SmpBox(); currentSmpBox = dummyBox; smpMap = new SmpMap( Constants.spaces[ Constants.emptySpace ], Constants.spaces[ Constants.emptySpace ]); smpBoxes = new Vector<SmpBox>(); dragRubber = new Rectangle(); setLayout( null ); setBackground( Color.white ); dim = getPreferredSize(); setSize( dim ); addComponentListener( this ); addMouseListener( this ); addMouseMotionListener( this ); setSmpMap( smpMap ); setFocusable( false ); } /** * Sample-Map zuweisen */ public void setSmpMap( SmpMap smpMap ) { SmpBox sb; clear(); synchronized( this.smpMap ) { this.smpMap = (SmpMap) smpMap.clone(); vSpaceLog = Math.log( smpMap.vSpace.max / smpMap.vSpace.min ); currentSmpBox = dummyBox; for( int i = 0; i < smpMap.size(); i++ ) { sb = new SmpBox(); sb.setName( getSmpName( smpMap.getSample( i ))); sb.addMouseListener( this ); sb.addMouseMotionListener( this ); add( sb ); smpBoxes.addElement( sb ); } } recalcScreenBoxes(); recalcBoxColors(); // Listener benachrichtigen // notifyListener( ACTION_BOXDESELECTED, -1 ); } /** * Sample-Map besorgen */ public SmpMap getSmpMap() { synchronized( smpMap ) { return (SmpMap) smpMap.clone(); } } /** * Alle Boxen entfernen */ public void clear() { synchronized( smpMap ) { currentSmpBox = dummyBox; removeAll(); smpBoxes.setSize( 0 ); while( smpMap.size() > 0 ) { smpMap.removeSample( 0 ); } } repaint(); // Listener benachrichtigen notifyListener( ACTION_BOXDESELECTED, -1 ); } /** * Dimensionen aendern * erzeugt eine neue Sample Map mit entsprechend beschnittenen/expandierten Boxen * (MOEGLICHERWEISE WERDEN EINIGE GELOESCHT; ES WERDEN KEINE BOXDELETED EVENTS GESENDET!) * NOTE: UNIT-WECHSEL WIRD FEHLERHAFT BEHANDELT!! * * @param hSpace neuer horizontaler Space, darf null sein * (= keine Aenderung) * @param vSpace neuer vertikaler Space, darf null sein * (= keine Aenderung) */ public void setSpaces( ParamSpace hSpace, ParamSpace vSpace ) { SmpMap newSmpMap; Vector<SmpBox> newBoxes; SmpZone smp; SmpBox sb; int newIndex; synchronized( smpMap ) { if( hSpace == null) hSpace = smpMap.hSpace; if( vSpace == null) vSpace = smpMap.vSpace; // Punkte skalieren newSmpMap = new SmpMap( hSpace, vSpace, smpMap.type ); newBoxes = new Vector<SmpBox>(); for( int index = smpMap.size() - 1; index >= 0; index-- ) { smp = smpMap.getSample( index ); sb = smpBoxes.elementAt( index ); if( Math.abs( smp.velLo.value - smpMap.hSpace.min ) < Constants.suckyDoubleError ) { // expand smp.velLo.value = hSpace.min; } else { smp.velLo.value = hSpace.fitValue( smp.velLo.value); } if( Math.abs( smp.velHi.value - smpMap.hSpace.max ) < Constants.suckyDoubleError ) { // expand smp.velHi.value = hSpace.max; } else { smp.velHi.value = hSpace.fitValue( smp.velHi.value); } if( Math.abs( smp.freqHi.value - smpMap.vSpace.max ) < Constants.suckyDoubleError ) { // expand smp.freqHi.value = vSpace.max; } else { smp.freqHi.value = vSpace.fitValue( smp.freqHi.value); } if( Math.abs( smp.freqLo.value - smpMap.vSpace.min ) < Constants.suckyDoubleError ) { // expand smp.freqLo.value = vSpace.min; } else { smp.freqLo.value = vSpace.fitValue( smp.freqLo.value); } if( ((smp.velHi.value - smp.velLo.value + Constants.suckyDoubleError) > hSpace.inc) && // not if too small ((smp.freqHi.value - smp.freqLo.value + Constants.suckyDoubleError) > vSpace.inc) ) { newIndex = newSmpMap.addSample( smp ); if( newIndex != -1 ) { newBoxes.insertElementAt( sb, newIndex ); } else { remove( sb ); } } else { remove( sb ); } } this.smpMap = newSmpMap; this.smpBoxes = newBoxes; vSpaceLog = Math.log( vSpace.max / vSpace.min ); currentSmpBox.setSelected( SB_STATE_NORMAL ); currentSmpBox = dummyBox; } recalcScreenBoxes(); recalcBoxColors(); // Listener benachrichtigen notifyListener( ACTION_BOXDESELECTED, -1 ); notifyListener( ACTION_SPACECHANGED, -1 ); } /** * Derzeitigen horizontalen Space ermitteln */ public ParamSpace getHSpace() { synchronized( smpMap ) { return smpMap.hSpace; } } /** * Derzeitigen vertikalen Space ermitteln */ public ParamSpace getVSpace() { synchronized( smpMap ) { return smpMap.vSpace; } } /** * Registriert einen ActionListener; * Action-Events kommen, wenn sich der Wert des ParamFieldes aendert */ public void addActionListener( ActionListener list ) { actionComponent.addActionListener( list ); } /** * Entfernt einen ActionListener */ public void removeActionListener( ActionListener list ) { actionComponent.removeActionListener( list ); } /** * Eine SampleZone besorgen * DAS OBJECT IST EIN CLONE! * * @param uniqueID bei -1 wird die aktuell angewaehlte Box genommen * * @return null, wenn Fehler oder keine Box angewaehlt */ public SmpZone getSample( int uniqueID ) { int index; SmpZone smp = null; synchronized( smpMap ) { if( uniqueID == -1 ) { index = smpBoxes.indexOf( currentSmpBox ); smp = smpMap.getSample( index ); if( smp == null ) return null; } else { for( index = 0; index < smpMap.size(); index++ ) { smp = smpMap.getSample( index ); if( (smp != null) && (smp.uniqueID == uniqueID) ) break; } if( (smp == null) || (smp.uniqueID != uniqueID) ) return null; } return( (SmpZone) smp.clone() ); } } /** * Neue Werte fuer aktuelle Sample-Zone mitteilen * DAS OBJECT WIRD GECLONT! * * @return false, wenn keine Box angewaehlt ist oder Werte ungueltig sind */ public boolean setSample( SmpZone smp ) { SmpBox sb = currentSmpBox; int index; int newIndex; SmpZone smpOld; synchronized( smpMap ) { index = smpBoxes.indexOf( sb ); smpOld = smpMap.getSample( index ); if( (smpOld == null) || (smpOld.uniqueID != smp.uniqueID) ) { // hmmm, must have changed?! for( index = 0; index < smpMap.size(); index++ ) { smpOld = smpMap.getSample( index ); if( smpOld.uniqueID == smp.uniqueID ) break; } if ((smpOld == null) || (smpOld.uniqueID != smp.uniqueID)) { GUIUtil.displayError(this, new NoSuchElementException(ModulePanel.ERR_CORRUPTED), "setSample"); return false; } sb.setSelected( SB_STATE_NORMAL ); sb = smpBoxes.elementAt( index ); sb.setSelected( SB_STATE_SELECTED ); currentSmpBox = sb; } smpMap.removeSample( index ); smpBoxes.removeElement( sb ); smp = (SmpZone) smp.clone(); newIndex = smpMap.addSample( smp ); sb.setName( getSmpName( smp )); if( newIndex == -1 ) { // restore old one newIndex = smpMap.addSample( smpOld ); if( newIndex == -1 ) { // fatal error GUIUtil.displayError(this, new IllegalStateException(ModulePanel.ERR_CORRUPTED), "setSample"); currentSmpBox.setSelected( SB_STATE_NORMAL ); currentSmpBox = dummyBox; notifyListener( ACTION_BOXDELETED, smp.uniqueID ); return false; } } smpBoxes.insertElementAt( sb, newIndex ); recalcScreenBox( sb ); if( newIndex != index ) { recalcBoxColors(); } // Listener benachrichtigen notifyListener( ACTION_BOXCHANGED, smp.uniqueID ); } return true; } /** * Box auf das Panel legen * * @return null bei Fehler (z.B. schon Box am selben Ort vorhanden) */ public SmpZone addSample( SmpZone smp ) { int index; SmpBox sb = new SmpBox(); sb.setName( getSmpName( smp )); sb.addMouseListener( this ); sb.addMouseMotionListener( this ); synchronized( smpMap ) { index = smpMap.addSample( smp ); if( index >= 0 ) { add( sb ); smpBoxes.insertElementAt( sb, index ); recalcScreenBox( sb ); recalcBoxColors(); // umschalten currentSmpBox.setSelected( SB_STATE_NORMAL ); currentSmpBox = sb; currentSmpBox.setSelected( SB_STATE_SELECTED ); // Listener benachrichtigen notifyListener( ACTION_BOXCREATED, smp.uniqueID ); return smp; } return null; } } /** * Box vom Panel entfernen */ public void removeBox( SmpBox sb ) { int index; SmpZone smp; synchronized( smpMap ) { index = smpBoxes.indexOf( sb ); if( index >= 0 ) { remove( sb ); smp = smpMap.getSample( index ); smpMap.removeSample( index ); smpBoxes.removeElement( sb ); recalcBoxColors(); currentSmpBox.setSelected( SB_STATE_NORMAL ); currentSmpBox = dummyBox; // Listener benachrichtigen notifyListener( ACTION_BOXDELETED, smp.uniqueID ); } } } public void paintComponent( Graphics g ) { super.paintComponent( g ); int hGrid, vGrid; // -------- Grid -------- hGrid = 1 << (int) Math.max( 0.0, Math.floor( Math.log( (float) dim.width / 20 ) / Constants.ln2 )); vGrid = 1 << (int) Math.max( 0.0, Math.floor( Math.log( (float) dim.height / 16 ) / Constants.ln2 )); g.setColor( Color.lightGray ); for( int i = 0; i <= hGrid; i++ ) { g.drawLine( (dim.width-1) * i / hGrid, 0, (dim.width-1) * i / hGrid, dim.height ); } for( int i = 0; i <= vGrid; i++ ) { g.drawLine( 0, (dim.height-1) * i / vGrid, dim.width, (dim.height-1) * i / vGrid ); } } public void setEnabled( boolean state ) { super.setEnabled( state ); if( currentSmpBox != dummyBox ) { currentSmpBox.setSelected( SB_STATE_NORMAL ); currentSmpBox = dummyBox; // Listener benachrichtigen notifyListener( ACTION_BOXDESELECTED, -1 ); } setBackground( state ? Color.white : getParent().getBackground() ); repaint(); } public Dimension getPreferredSize() { return new Dimension( 64, 64 ); } public Dimension getMinimumSize() { return getPreferredSize(); } // -------- Component methods (nur Panel!) -------- public void componentResized( ComponentEvent e ) { dim = getSize(); recalcScreenBoxes(); repaint(); } public void componentMoved( ComponentEvent e ) {} public void componentHidden( ComponentEvent e ) {} public void componentShown( ComponentEvent e ) {} // -------- Mouse Listener methods -------- public void mouseClicked( MouseEvent e ) { DoublePoint dp1, dp2; SmpZone smp; Rectangle r1, r2; Point p1, p2; Graphics g; if( !isEnabled() ) return; // not while inactive if( e.getSource() == this ) { //-------- Panel hit ------------------- if( (e.getClickCount() == 2) && !e.isAltDown() ) { dp1 = new DoublePoint(); dp2 = new DoublePoint(); if( findFreePlaceAround( e.getX(), e.getY(), dp1, dp2 )) { synchronized( smpMap ) { smp = new SmpZone( new Param( dp1.y, smpMap.vSpace.unit ), new Param( dp2.y, smpMap.vSpace.unit ), new Param( dp2.x, smpMap.hSpace.unit ), new Param( dp1.x, smpMap.hSpace.unit )); } p1 = paramSpaceToScreen( dp1.x, dp1.y ); p2 = paramSpaceToScreen( dp2.x, dp2.y ); r1 = new Rectangle( e.getX(), e.getY(), 1, 1 ); r2 = new Rectangle( p1.x, p1.y, p2.x - p1.x - 1, p2.y - p1.y - 1 ); g = getGraphics(); g.setColor( Color.black ); g.setXORMode( getBackground() ); GUISupport.rubberGlide( r1, r2, g ); g.dispose(); addSample( smp ); } } } else { if( e.isAltDown() ) { removeBox( (SmpBox) e.getSource() ); } } } public void mousePressed( MouseEvent e ) { Rectangle sbBounds; int index; if( !isEnabled() ) return; // not while disabled // alte SmpBox deselektieren currentSmpBox.setSelected( SB_STATE_NORMAL ); if( e.getSource() == this ) { //-------- Panel hit ------------------- if( currentSmpBox != dummyBox ) { currentSmpBox = dummyBox; // Listener benachrichtigen notifyListener( ACTION_BOXDESELECTED, -1 ); } if( e.isControlDown() ) { // PopUp-Menu // XXX } } else { //-------- SmpBox hit ------------------ currentSmpBox = (SmpBox) e.getSource(); sbBounds = currentSmpBox.getBounds(); // neue SmpBox selektieren currentSmpBox.setSelected( SB_STATE_SELECTED ); if( e.isControlDown() ) { // PopUp-Menu // XXX } else if( !e.isAltDown() ) { // prepare Drag dragOriginX = e.getX(); dragOriginY = e.getY(); dragBounds = sbBounds; dragSource = currentSmpBox; synchronized( smpMap ) { index = smpBoxes.indexOf( dragSource ); if( index >= 0 ) { dragSmp = smpMap.getSample( index ); dragType = DRAG_NONE; if( e.getY() < 4 ) { dragType |= DRAG_TOP; } else if( (sbBounds.height - e.getY()) < 5 ) { dragType |= DRAG_BOTTOM; } if( e.getX() < 4 ) { dragType |= DRAG_LEFT; } else if( (sbBounds.width - e.getX()) < 5 ) { dragType |= DRAG_RIGHT; } if( dragType == DRAG_NONE ) dragType = DRAG_MOVE; } } } } } public void mouseReleased( MouseEvent e ) { Graphics g, g2; SmpZone smp; int index; if( !isEnabled() ) return; // not while running the operators if( dragState ) { // clear rubberband g = getGraphics(); g.setColor( Color.black ); g.setXORMode( getBackground() ); g2 = dragSource.getGraphics(); g2.setColor( Color.black ); g2.setXORMode( Color.black ); g2.setClip( 1, 1, dragBounds.width - 2, dragBounds.height - 2 ); g.drawRect( dragRubber.x, dragRubber.y, dragRubber.width - 1, dragRubber.height - 1 ); g2.drawRect( dragRubber.x - dragBounds.x, dragRubber.y - dragBounds.y, dragRubber.width - 1, dragRubber.height - 1 ); smp = (SmpZone) dragSmp.clone(); smp.freqHi.value = dragTopLeft.y; smp.freqLo.value = dragBottomRight.y; smp.velHi.value = dragBottomRight.x; smp.velLo.value = dragTopLeft.x; if( !setSample( smp )) { // failed to "resize" the zone GUISupport.rubberGlide( dragRubber, dragBounds, g ); notifyListener( ACTION_BOXSELECTED, smp.uniqueID ); } g.dispose(); g2.dispose(); dragState = false; setCursor( lastCursor ); } else { // Listener benachrichtigen if( (currentSmpBox != dummyBox) && !e.isAltDown() ) { index = smpBoxes.indexOf( currentSmpBox ); smp = smpMap.getSample( index ); if( smp != null ) { notifyListener( ACTION_BOXSELECTED, smp.uniqueID ); } } } dragType = DRAG_NONE; dragSource = null; } public void mouseEntered( MouseEvent e ) {} public void mouseExited( MouseEvent e ) {} // -------- MouseMotion Listener methods -------- public void mouseDragged( MouseEvent e ) { Graphics g, g2; int dist; DoublePoint dp1, dp2; DoublePoint otl, obr; Point p1, p2; if( !isEnabled() || (dragType == DRAG_NONE) ) return; g = getGraphics(); g.setColor( Color.black ); g.setXORMode( getBackground() ); g2 = dragSource.getGraphics(); g2.setColor( Color.black ); g2.setXORMode( Color.black ); g2.setClip( 1, 1, dragBounds.width - 2, dragBounds.height - 2 ); if( !dragState ) { // check if distance is ok to start drag dist = (e.getX() - dragOriginX) * (e.getX() - dragOriginX) + (e.getY() - dragOriginY) * (e.getY() - dragOriginY); if( (dragType == DRAG_MOVE) && (dist <= 16) ) { g.dispose(); g2.dispose(); return; // ...not ok } lastCursor = getCursor(); setCursor( new Cursor( cursorID[ dragType ])); dragState = true; dragRubber.setBounds( dragSource.getBounds() ); dragTopLeft = new DoublePoint( dragSmp.velLo.value, dragSmp.freqHi.value); dragBottomRight = new DoublePoint( dragSmp.velHi.value, dragSmp.freqLo.value); } else { // clear rubberband g.drawRect( dragRubber.x, dragRubber.y, dragRubber.width - 1, dragRubber.height - 1 ); g2.drawRect( dragRubber.x - dragBounds.x, dragRubber.y - dragBounds.y, dragRubber.width - 1, dragRubber.height - 1 ); } // Screen-Koordinaten berechnen: l/r if( (dragType & DRAG_LEFT) != 0 ) { dragRubber.x = dragBounds.x + e.getX() - dragOriginX; if( (dragType & DRAG_RIGHT) == 0 ) { dragRubber.width = dragBounds.width - e.getX() + dragOriginX; } } else { if( (dragType & DRAG_RIGHT) != 0 ) { dragRubber.width = dragBounds.width + e.getX() - dragOriginX; } } if( dragRubber.x < 0 ) { if( (dragType & DRAG_RIGHT) == 0 ) { dragRubber.width += dragRubber.x; } dragRubber.x = 0; } if( (dragRubber.x + dragRubber.width) > dim.width ) { if( (dragType & DRAG_LEFT) != 0 ) { dragRubber.x = dim.width - dragRubber.width; } else { dragRubber.width = dim.width - dragRubber.x + 1; } } // Screen-Koordinaten berechnen: t/b if( (dragType & DRAG_TOP) != 0 ) { dragRubber.y = dragBounds.y + e.getY() - dragOriginY; if( (dragType & DRAG_BOTTOM) == 0 ) { dragRubber.height = dragBounds.height - e.getY() + dragOriginY; } } else { if( (dragType & DRAG_BOTTOM) != 0 ) { dragRubber.height = dragBounds.height + e.getY() - dragOriginY; } } if( dragRubber.y < 0 ) { if( (dragType & DRAG_BOTTOM) == 0 ) { dragRubber.height += dragRubber.y; } dragRubber.y = 0; } if( (dragRubber.y + dragRubber.height) > dim.height ) { if( (dragType & DRAG_TOP) != 0 ) { dragRubber.y = dim.height - dragRubber.height; } else { dragRubber.height = dim.height - dragRubber.y + 1; } } dp1 = screenToParamSpace( dragRubber.x, dragRubber.y ); dp2 = screenToParamSpace( dragRubber.x + dragRubber.width - 1, dragRubber.y + dragRubber.height - 1 ); otl = (DoublePoint) dragTopLeft.clone(); obr = (DoublePoint) dragBottomRight.clone(); // Space-Koordinaten aendern if( (dragType & DRAG_LEFT) != 0 ) { if( e.getX() != dragOriginX ) { dragTopLeft.x = dp1.x; } else { dragTopLeft.x = dragSmp.velLo.value; // keine Rundungsfehler wenn derselbe Screen-Pixel } } if( (dragType & DRAG_TOP) != 0 ) { if( e.getY() != dragOriginY ) { dragTopLeft.y = dp1.y; } else { dragTopLeft.y = dragSmp.freqHi.value; } } if( (dragType & DRAG_RIGHT) != 0 ) { if( e.getX() != dragOriginX ) { dragBottomRight.x = dp2.x; } else { dragBottomRight.x = dragSmp.velHi.value; } } if( (dragType & DRAG_BOTTOM) != 0 ) { if( e.getY() != dragOriginY ) { dragBottomRight.y = dp2.y; } else { dragBottomRight.y = dragSmp.freqLo.value; } } // dragTopLeft/BottomRight evtl. beschneiden cutTheCheese( dragSmp, dragTopLeft, dragBottomRight, otl, obr ); p1 = paramSpaceToScreen( dragTopLeft.x, dragTopLeft.y ); p2 = paramSpaceToScreen( dragBottomRight.x, dragBottomRight.y ); dragRubber.setBounds( p1.x, p1.y, p2.x - p1.x + 1, p2.y - p1.y + 1 ); // current one g.drawRect( dragRubber.x, dragRubber.y, dragRubber.width - 1, dragRubber.height - 1 ); g2.drawRect( dragRubber.x - dragBounds.x, dragRubber.y - dragBounds.y, dragRubber.width - 1, dragRubber.height - 1 ); g.dispose(); g2.dispose(); } public void mouseMoved( MouseEvent e ) {} // -------- private methods -------- protected void notifyListener( String actionStr, int smpID ) { ActionEvent e; e = new ActionEvent( this, ActionEvent.ACTION_PERFORMED, actionStr + smpID ); actionComponent.dispatchEvent( e ); } /* * Berechnet die Position der Boxen neu */ protected void recalcScreenBoxes() { synchronized( smpMap ) { for( int i = 0; i < smpBoxes.size(); i++ ) { recalcScreenBox(smpBoxes.elementAt( i )); } } } /* * Berechnet die Position einer Box neu */ protected void recalcScreenBox( SmpBox sb ) { int index; SmpZone smp; Point loc1, loc2, loc3; synchronized( smpMap ) { index = smpBoxes.indexOf( sb ); smp = smpMap.getSample( index ); if( (sb != null) && (smp != null) ) { loc1 = paramSpaceToScreen( smp.velLo.value, smp.freqHi.value); loc2 = paramSpaceToScreen( smp.velHi.value, smp.freqLo.value); loc3 = paramSpaceToScreen( smp.velLo.value, smp.base.value); sb.setBase( loc3.y - loc1.y ); sb.setBounds( loc1.x, loc1.y, loc2.x - loc1.x + 1, loc2.y - loc1.y + 1 ); } } } /* * Weist den Boxen die Farben neu zu */ protected void recalcBoxColors() { SmpBox sb; int num; synchronized( smpMap ) { num = smpBoxes.size(); for( int i = 0; i < num; i++ ) { sb = smpBoxes.elementAt( i ); sb.setColor( (float) i / (float) num ); } } } /* * Extrahiert logischen Samplenamen */ protected String getSmpName( SmpZone smp ) { int i1, i2; String name = smp.fileName; i1 = name.lastIndexOf( File.separatorChar ) + 1; i2 = name.lastIndexOf( '.' ); if( i2 < i1 ) { i2 = name.length(); } return( name.substring( i1, i2 )); } /* * Uebersetzt Bildschirm-Koordinaten in Parameter-Koordinaten */ protected DoublePoint screenToParamSpace( int x, int y ) { double dx, dy; synchronized( smpMap ) { dx = smpMap.hSpace.min + (smpMap.hSpace.max - smpMap.hSpace.min) * x / (dim.width-1); dy = Math.exp( (1.0 - (double) y / (double) (dim.height-1)) * vSpaceLog ) * smpMap.vSpace.min; // dy = smpMap.vSpace.max - (smpMap.vSpace.max - smpMap.vSpace.min) * y / (dim.height-1); return new DoublePoint( smpMap.hSpace.fitValue( dx ), smpMap.vSpace.fitValue( dy )); } } /* * Uebersetzt Parameter-Koordinaten in Bildschirm-Koordinaten */ protected Point paramSpaceToScreen( double dx, double dy ) { int x, y; synchronized( smpMap ) { x = (int) ((dim.width-1) * (dx - smpMap.hSpace.min) / (smpMap.hSpace.max - smpMap.hSpace.min) + 0.5); y = (int) ((dim.height-1) * (1.0 - Math.log( dy / smpMap.vSpace.min ) / vSpaceLog)); // y = (int) ((double) (dim.height-1) * // (smpMap.vSpace.max - dy) / (smpMap.vSpace.max - smpMap.vSpace.min) + 0.5); return new Point( x, y ); } } /* * Findet freien Platz fuer neue Zone * * @param x Ausgangs-Punkt, X-Koordinate * @param y Ausgangs-Punkt, Y-Koordinate * @param tl wird gefuellt mit Koordinaten links/oben * @param br wird gefuellt mit Koordinaten rechts/unten * @return false, wenn kein freier Platz gefunden wurde */ protected boolean findFreePlaceAround( int x, int y, DoublePoint tl, DoublePoint br ) { int num, i, j; SmpZone smp; double velMid, freqMid; // Space-Repraesentation der Parameter x, y double velLo, velHi, freqLo, freqHi; // Space-Rechteck boolean iHoriz, iVert; // possible intersections double velCutLo[], velCutHi[], freqCutLo[], freqCutHi[]; // Verkleinerungsalternativen int cutNum, bestCut; double aspect, bestAspect, spaceAspect; Point pt1, pt2; // Screen-Rechteck DoublePoint dp; velCutLo = new double[ 4 ]; velCutHi = new double[ 4 ]; freqCutLo = new double[ 4 ]; freqCutHi = new double[ 4 ]; dp = screenToParamSpace( x, y ); velMid = dp.x; freqMid = dp.y; synchronized( smpMap ) { velLo = smpMap.hSpace.min; // initial rectangle = biggest one possible on the map velHi = smpMap.hSpace.max; freqLo = smpMap.vSpace.min; freqHi = smpMap.vSpace.max; spaceAspect = (freqHi - freqLo) / (velHi - velLo); num = smpMap.size(); for( i = 0; i < num; i++ ) { // step through all zones smp = smpMap.getSample( i ); if( smp == null ) continue; // actually data is corrupted; anyway ;) iHoriz = (smp.velLo.value < velHi) && (smp.velHi.value > velLo); iVert = (smp.freqHi.value > freqLo) && (smp.freqLo.value < freqHi); if( !iHoriz || !iVert ) continue; // no intersection cutNum = 0; if( (smp.velHi.value <= velMid) && (smp.velHi.value < velHi) ) { // cut left border velCutLo[ cutNum ] = smp.velHi.value; velCutHi[ cutNum ] = velHi; freqCutLo[ cutNum ] = freqLo; freqCutHi[ cutNum ] = freqHi; cutNum++; } if( (smp.velLo.value >= velMid) && (smp.velLo.value > velLo) ) { // cut right border velCutLo[ cutNum ] = velLo; velCutHi[ cutNum ] = smp.velLo.value; freqCutLo[ cutNum ] = freqLo; freqCutHi[ cutNum ] = freqHi; cutNum++; } if( (smp.freqLo.value >= freqMid) && (smp.freqLo.value > freqLo) ) { // cut top border velCutLo[ cutNum ] = velLo; velCutHi[ cutNum ] = velHi; freqCutLo[ cutNum ] = freqLo; freqCutHi[ cutNum ] = smp.freqLo.value; cutNum++; } if( (smp.freqHi.value <= freqMid) && (smp.freqHi.value < freqHi) ) { // cut bottom velCutLo[ cutNum ] = velLo; velCutHi[ cutNum ] = velHi; freqCutLo[ cutNum ] = smp.freqHi.value; freqCutHi[ cutNum ] = freqHi; cutNum++; } // ((SmpBox) smpBoxes.elementAt( i )).setSelected( SB_STATE_SELECTED ); // Graphics g2 = ((SmpBox) smpBoxes.elementAt( i )).getGraphics(); // ((SmpBox) smpBoxes.elementAt( i )).paint( g2 ); bestAspect = 0.0; bestCut = -1; for( j = 0; j < cutNum; j++ ) { // find "best" cut (i.e. the one mostly quadratic) // Graphics g = getGraphics(); // g.setXORMode( getBackground() ); // pt1 = paramSpaceToScreen( velCutLo[ j ], freqCutHi[ j ] ); // pt2 = paramSpaceToScreen( velCutHi[ j ], freqCutLo[ j ] ); // g.drawRect( pt1.x, pt1.y, pt2.x - pt1.x - 1, pt2.y - pt1.y - 1 ); // g.drawLine( pt1.x, pt1.y, pt2.x, pt2.y ); // g.drawLine( pt1.x, pt2.y, pt2.x, pt1.y ); // try { Thread.currentThread().sleep( 1000 ); } catch( InterruptedException e99 ) {} // g.drawRect( pt1.x, pt1.y, pt2.x - pt1.x - 1, pt2.y - pt1.y - 1 ); // g.drawLine( pt1.x, pt1.y, pt2.x, pt2.y ); // g.drawLine( pt1.x, pt2.y, pt2.x, pt1.y ); // g.dispose(); aspect = (velCutHi[ j ] - velCutLo[ j ]) / (freqCutHi[ j ] - freqCutLo[ j ]) * spaceAspect; if( aspect > 1.0 ) aspect = 1/aspect; if( aspect > bestAspect ) { bestAspect = aspect; bestCut = j; } } // try { Thread.currentThread().sleep( 1000 ); } catch( InterruptedException e98 ) {} // ((SmpBox) smpBoxes.elementAt( i )).setSelected( SB_STATE_NORMAL ); // ((SmpBox) smpBoxes.elementAt( i )).paint( g2 ); // g2.dispose(); if( bestCut == -1 ) return false; // no place to grow velLo = velCutLo[ bestCut ]; velHi = velCutHi[ bestCut ]; freqLo = freqCutLo[ bestCut ]; freqHi = freqCutHi[ bestCut ]; } // loop } tl.x = velLo; tl.y = freqHi; br.x = velHi; br.y = freqLo; pt1 = paramSpaceToScreen( velLo, freqHi ); pt2 = paramSpaceToScreen( velHi, freqLo ); return !(((pt2.x - pt1.x) < 6) || ((pt2.y - pt1.y) < 6)); } /* * Beschneidet eine SmpZone-Flaeche, so dass sie nicht mit anderen Zonen ueberlappt * * @param tl zu beschneidende Koordinaten links/oben * @param br zu beschneidende Koordinaten rechts/unten * @param otl alte Koordinate * @param obr alte Koordinate * @param dragType DRAG_LEFT etc. * @return false, wenn Beschnitt nicht moeglich (vorherige Koordinaten * sollten wiederhergestellt werden) */ protected boolean cutTheCheese( SmpZone smp, DoublePoint tl, DoublePoint br, DoublePoint otl, DoublePoint obr ) { int num; SmpZone smp2; boolean iHoriz, iVert; boolean orientL, orientR, orientT, orientB; double freqSpan, velSpan; boolean intersected; int i; long gedultsFaden = System.currentTimeMillis() + 500; freqSpan = smp.freqHi.value - smp.freqLo.value; velSpan = smp.velHi.value - smp.velLo.value; synchronized( smpMap ) { num = smpMap.size(); do { for( i = 0, intersected = false; i < num; i++ ) { // step through all zones smp2 = smpMap.getSample( i ); if( (smp2 == null) || (smp2 == smp) ) continue; // actually data is corrupted; anyway iHoriz = (smp2.velLo.value < br.x) && (smp2.velHi.value > tl.x); iVert = (smp2.freqHi.value > br.y) && (smp2.freqLo.value < tl.y); if( !iHoriz || !iVert ) continue; // no intersection intersected = true; orientL = obr.x <= smp2.velLo.value + Constants.suckyDoubleError; orientR = otl.x >= smp2.velHi.value - Constants.suckyDoubleError; orientT = obr.y >= smp2.freqHi.value - Constants.suckyDoubleError; orientB = otl.y <= smp2.freqLo.value + Constants.suckyDoubleError; if( orientL && ((dragType & DRAG_RIGHT) != 0) ) { br.x = smp2.velLo.value; if( (dragType & DRAG_LEFT) != 0 ) { tl.x = br.x - velSpan; } } if( orientR && ((dragType & DRAG_LEFT) != 0) ) { tl.x = smp2.velHi.value; if( (dragType & DRAG_RIGHT) != 0 ) { br.x = tl.x + velSpan; } } if( orientT && ((dragType & DRAG_BOTTOM) != 0) ) { br.y = smp2.freqHi.value; if( (dragType & DRAG_TOP) != 0 ) { tl.y = br.y + freqSpan; } } if( orientB && ((dragType & DRAG_TOP) != 0) ) { tl.y = smp2.freqLo.value; if( (dragType & DRAG_BOTTOM) != 0 ) { br.y = tl.y - freqSpan; } } } } while( intersected && (System.currentTimeMillis() < gedultsFaden) ); if( intersected ) { tl.x = otl.x; tl.y = otl.y; br.x = obr.x; br.y = obr.y; } } return !intersected; } // -------- interne SmpBox-Klasse -------- class SmpBox extends Canvas { // ........ private variables ........ // Status wie STATE_NORMAL, selektiert etc. private int state = SmpMapPanel.SB_STATE_UNKNOWN; private int base = 0; private String name = ""; private int nameWidth = 0; private Font fnt; private FontMetrics fntMetr; private Color normalColor = SystemColor.control; private Color activeColor = OpIcon.selectColor; // ........ public methods ........ public SmpBox() { super(); Font fnt = getFont(); fntMetr = getFontMetrics( fnt ); setSelected( SmpMapPanel.SB_STATE_NORMAL ); setSize( getPreferredSize() ); setLocation( 0, 0 ); // Event handling enableEvents(AWTEvent.MOUSE_EVENT_MASK); setFocusable( false ); } /** * Basisfrequenz-Linie setzen */ public void setBase( int base ) { if( this.base != base ) { this.base = base; repaint(); } } /** * Namen setzen */ public void setName( String name ) { this.name = name; nameWidth = fntMetr.stringWidth( name ); repaint(); } /** * Status veraendern * * @return vorheriger Status */ public int setSelected( int state ) { int lastState = this.state; this.state = state; if( lastState != state ) { if( state == SmpMapPanel.SB_STATE_NORMAL ) { setForeground( normalColor ); setBackground( normalColor ); } else { setForeground( activeColor ); setBackground( activeColor ); } repaint(); } return lastState; } public int isSelected() { return state; } /** * Farbe aendern * * @param hue Farbwert im HSB System (0...1) */ public void setColor( float hue ) { int lastState = this.state; this.state = SmpMapPanel.SB_STATE_UNKNOWN; normalColor = new Color( Color.HSBtoRGB( hue, 0.4f, 0.8f )); activeColor = new Color( Color.HSBtoRGB( hue, 1.0f, 0.6f )); setSelected( lastState ); // invokes repaint() } public void paint( Graphics g ) { Dimension d = getSize(); g.draw3DRect( 1, 1, d.width - 3, d.height - 3, true ); g.setColor( Color.black ); g.drawRect( 0, 0, d.width - 1, d.height - 1 ); if( state == SmpMapPanel.SB_STATE_SELECTED ) { g.setColor( Color.white ); } // Basisfrequenz symbolisieren if( base < 2 ) { // arrow up g.drawLine( d.width - 11, 5, d.width - 7, 1 ); g.drawLine( d.width - 6, 2, d.width - 3, 5 ); } else if( base < d.height - 2 ) { // line if( base > fntMetr.getHeight() ) { g.drawLine( 2, base, d.width - 3, base ); } else { g.drawLine( 3 + nameWidth, base, d.width - 3, base ); } } else { // arrow down g.drawLine( d.width - 11, d.height - 6, d.width - 7, d.height - 2 ); g.drawLine( d.width - 6, d.height - 3, d.width - 3, d.height - 6 ); } g.drawString( name, 2, fntMetr.getAscent() + 1 ); } public Dimension getPreferredSize() { return new Dimension( 6, 6 ); } public Dimension getMinimumSize() { return getPreferredSize(); } // ........ private methods ........ protected void processMouseEvent( MouseEvent e ) { if( e.getID() == MouseEvent.MOUSE_PRESSED ) { requestFocus(); } super.processMouseEvent( e ); } } // class SmpBox }