/* * DocumentFrame.java * Eisenkraut * * Copyright (c) 2004-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.eisenkraut.session; import de.sciss.app.AbstractApplication; import de.sciss.app.AbstractWindow; import de.sciss.app.DynamicPrefChangeManager; import de.sciss.app.GraphicsHandler; import de.sciss.common.AppWindow; import de.sciss.common.BasicApplication; import de.sciss.common.BasicMenuFactory; import de.sciss.common.BasicWindowHandler; import de.sciss.common.ProcessingThread; import de.sciss.common.ShowWindowAction; import de.sciss.eisenkraut.Main; import de.sciss.eisenkraut.edit.BasicCompoundEdit; import de.sciss.eisenkraut.edit.TimelineVisualEdit; import de.sciss.eisenkraut.gui.AbstractTool; import de.sciss.eisenkraut.gui.AudioFileInfoPalette; import de.sciss.eisenkraut.gui.CrossfadePanel; import de.sciss.eisenkraut.gui.GraphicsUtil; import de.sciss.eisenkraut.gui.MenuFactory; import de.sciss.eisenkraut.gui.ObserverPalette; import de.sciss.eisenkraut.gui.PeakMeterManager; import de.sciss.eisenkraut.gui.ProgressPanel; import de.sciss.eisenkraut.gui.RecorderDialog; import de.sciss.eisenkraut.gui.ToolAction; import de.sciss.eisenkraut.gui.ToolActionEvent; import de.sciss.eisenkraut.gui.ToolActionListener; import de.sciss.eisenkraut.gui.WaveformView; import de.sciss.eisenkraut.io.AudioTrail; import de.sciss.eisenkraut.io.DecimatedSonaTrail; import de.sciss.eisenkraut.io.DecimatedTrail; import de.sciss.eisenkraut.io.DecimatedWaveTrail; import de.sciss.eisenkraut.io.DecimationInfo; import de.sciss.eisenkraut.io.MarkerTrail; import de.sciss.eisenkraut.net.SuperColliderClient; import de.sciss.eisenkraut.net.SuperColliderPlayer; import de.sciss.eisenkraut.realtime.Transport; import de.sciss.eisenkraut.realtime.TransportListener; import de.sciss.eisenkraut.realtime.TransportToolBar; import de.sciss.eisenkraut.render.FilterDialog; import de.sciss.eisenkraut.render.RenderPlugIn; import de.sciss.eisenkraut.timeline.AudioTrack; import de.sciss.eisenkraut.timeline.AudioTrackRowHeader; import de.sciss.eisenkraut.timeline.MarkerAxis; import de.sciss.eisenkraut.timeline.TimelineAxis; import de.sciss.eisenkraut.timeline.TimelineEvent; import de.sciss.eisenkraut.timeline.TimelineListener; import de.sciss.eisenkraut.timeline.TimelineScroll; import de.sciss.eisenkraut.timeline.TimelineToolBar; import de.sciss.eisenkraut.timeline.Track; import de.sciss.eisenkraut.timeline.TrackRowHeader; import de.sciss.eisenkraut.util.PrefsUtil; import de.sciss.gui.AbstractWindowHandler; import de.sciss.gui.Axis; import de.sciss.gui.ComponentBoundsRestrictor; import de.sciss.gui.ComponentHost; import de.sciss.gui.CoverGrowBox; import de.sciss.gui.GUIUtil; import de.sciss.gui.GradientPanel; import de.sciss.gui.MenuAction; import de.sciss.gui.MenuRoot; import de.sciss.gui.ModificationButton; import de.sciss.gui.PathField; import de.sciss.gui.PeakMeter; import de.sciss.gui.PeakMeterGroup; import de.sciss.gui.ProgressComponent; import de.sciss.gui.SpringPanel; import de.sciss.gui.StretchedGridLayout; import de.sciss.gui.TopPainter; import de.sciss.gui.TreeExpanderButton; import de.sciss.gui.VectorSpace; import de.sciss.io.AudioFileDescr; import de.sciss.io.AudioFileFormatPane; import de.sciss.io.IOUtil; import de.sciss.io.Marker; import de.sciss.io.Span; import de.sciss.timebased.Trail; import de.sciss.util.Flag; import javax.swing.*; import javax.swing.event.MouseInputAdapter; import javax.swing.undo.CompoundEdit; import javax.swing.undo.UndoableEdit; import java.awt.*; import java.awt.datatransfer.Clipboard; import java.awt.datatransfer.ClipboardOwner; import java.awt.datatransfer.StringSelection; import java.awt.datatransfer.Transferable; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; import java.awt.event.InputEvent; import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.io.File; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.MalformedURLException; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.prefs.PreferenceChangeEvent; import java.util.prefs.PreferenceChangeListener; import java.util.prefs.Preferences; public class DocumentFrame extends AppWindow implements ProgressComponent, TimelineListener, ClipboardOwner, ToolActionListener, DecimatedWaveTrail.AsyncListener, TransportListener, PreferenceChangeListener, SwingConstants { protected final Session doc; private final TimelineAxis timeAxis; protected final MarkerAxis markAxis; protected final TrackRowHeader markAxisHeader; protected final TimelineScroll scroll; protected final Transport transport; protected Span timelineSel; protected Span timelineVis; protected long timelinePos; protected long timelineLen; protected double timelineRate; protected final WaveformView waveView; protected final ComponentHost wavePanel; protected final JPanel channelHeaderPanel; private final JPanel flagsPanel; private final JPanel rulersPanel; private final JPanel metersPanel; private final List<AudioTrackRowHeader> collChannelHeaders = new ArrayList<AudioTrackRowHeader>(); protected final List<Axis> collChannelRulers = new ArrayList<Axis>(); private PeakMeter[] channelMeters = new PeakMeter[ 0 ]; private final JLabel lbSRC; protected final TreeExpanderButton ggTreeExp; private DecimatedTrail asyncTrail = null; // --- tools --- private final Map<Integer, TimelineTool> tools = new HashMap<Integer, TimelineTool>(); private AbstractTool activeTool = null; private final TimelinePointerTool pointerTool; // --- actions --- private final static String plugInPackage = "de.sciss.eisenkraut.render."; private final static String fscapePackage = "de.sciss.fscape.render."; private final ActionRevealFile actionRevealFile; private final ActionNewFromSel actionNewFromSel; protected final ActionClose actionClose; protected final ActionSave actionSave; protected final ActionSaveAs actionSaveAs; private final ActionSaveAs actionSaveSelectionAs; private final MenuAction actionProcess; protected final ActionProcessAgain actionProcessAgain; protected final ActionScroll actionZoomAllOut; private final AbstractWindow.Adapter winListener; private final JLabel lbWriteProtected; private boolean writeProtected = false; protected boolean wpHaveWarned = false; private final ShowWindowAction actionShowWindow; private static final String smpPtrn = "ch.{3} @ {0,number,0}"; private static final String timePtrn = "ch.{3} @ {1,number,integer}:{2,number,00.000}"; protected final MessageFormat msgCsr1 = new MessageFormat( timePtrn, Locale.US ); protected final MessageFormat msgCsr2PCMFloat = new MessageFormat( "{4,number,0.000} ({5,number,0.00} dBFS)", Locale.US ); protected final MessageFormat msgCsr3PCMInt = new MessageFormat( "= {6,number,0} @ {7,number,integer}-bit int", Locale.US ); protected final MessageFormat msgCsr2Peak = new MessageFormat( "peak {4,number,0.000} ({5,number,0.00} dBFS)", Locale.US ); protected final MessageFormat msgCsr3RMS = new MessageFormat( "eff {6,number,0.000} ({7,number,0.00} dBFS)", Locale.US ); protected int csrInfoBits; protected boolean csrInfoIsInt; protected static final double TWENTYDIVLOG10 = 20 / Math.log( 10 ); // --------- former viewport --------- // --- painting --- // private final boolean isDark = UIManager.getBoolean("dark-skin"); private final Color colrSelection = GraphicsUtil.colrSelection(); private final Color colrSelection2 = GraphicsUtil.colrInactiveSelection(); // selected timeline span over unselected trns protected final Color colrPosition = GraphicsUtil.setAlpha(GraphicsUtil.colrRed(), 0x7F); protected final Color colrZoom = new Color( 0xA0, 0xA0, 0xA0, 0x7F ); protected Rectangle vpRecentRect = new Rectangle(); protected int vpPosition = -1; private Rectangle vpPositionRect = new Rectangle(); protected final ArrayList<Rectangle> vpSelections = new ArrayList<Rectangle>(); protected final ArrayList<Color> vpSelectionColors = new ArrayList<Color>(); protected Rectangle vpSelectionRect = new Rectangle(); private Rectangle vpUpdateRect = new Rectangle(); protected Rectangle vpZoomRect = null; private float[] vpDash = { 3.0f, 5.0f }; private float vpScale; protected final Stroke[] vpZoomStroke = { new BasicStroke(2f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_MITER, 1.0f, vpDash, 0.0f), new BasicStroke(2f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_MITER, 1.0f, vpDash, 4.0f), new BasicStroke(2f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_MITER, 1.0f, vpDash, 6.0f), }; protected int vpZoomStrokeIdx = 0; protected boolean waveExpanded = true; // XXX should keep that in some prefs protected boolean viewMarkers; protected boolean markVisible; private boolean chanMeters = false; private boolean forceMeters = false; protected final TimelineToolBar timeTB; private final TransportToolBar transTB; // --- progress bar --- private final JLabel /* JTextField */ ggAudioFileDescr; private final ProgressPanel pProgress; private final CrossfadePanel pOverlay; private final boolean internalFrames; protected final BasicApplication app; private final PeakMeterManager lmm; protected boolean disposed = false; private final Timer playTimer; private double playRate = 1.0; protected final ComponentBoundsRestrictor cbr; private static Point lastLeftTop = new Point(); private static final String KEY_TRACKSIZE = "tracksize"; private int verticalScale; protected static final Cursor[] zoomCsr; static { final Toolkit tk = Toolkit.getDefaultToolkit(); final Point hotSpot = new Point(7, 7); zoomCsr = new Cursor[]{ tk.createCustomCursor(tk.createImage( ToolAction.class.getResource("zoomin.png")), hotSpot, "zoom-in"), tk.createCustomCursor(tk.createImage( ToolAction.class.getResource("zoomout.png")), hotSpot, "zoom-out") }; } /** * Constructs a new timeline window with * all the sub elements. Installs the * global key commands. (a DocumentFrame * should be created only once in the application). * * @param doc session Session */ public DocumentFrame(final Session doc) { super(REGULAR); app = (BasicApplication) AbstractApplication.getApplication(); this.doc = doc; transport = doc.getTransport(); timelinePos = doc.timeline.getPosition(); timelineSel = doc.timeline.getSelectionSpan(); timelineVis = doc.timeline.getVisibleSpan(); timelineRate = doc.timeline.getRate(); timelineLen = doc.timeline.getLength(); SuperColliderClient superCollider = SuperColliderClient.getInstance(); lmm = new PeakMeterManager( superCollider.getMeterManager() ); final Container cp = getContentPane(); final InputMap iMap = getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); final ActionMap aMap = getActionMap(); final AbstractButton ggAudioInfo, ggRevealFile; final int myMeta = BasicMenuFactory.MENU_SHORTCUT == InputEvent.CTRL_MASK ? InputEvent.CTRL_MASK | InputEvent.SHIFT_MASK : BasicMenuFactory.MENU_SHORTCUT; // META on Mac, CTRL+SHIFT on PC final TopPainter trackPainter; final MenuRoot mr; final JPanel topPane = GUIUtil.createGradientPanel(); Box box; internalFrames = app.getWindowHandler().usesInternalFrames(); timeTB = new TimelineToolBar (doc); transTB = new TransportToolBar(doc); wavePanel = new ComponentHost(); timeAxis = new TimelineAxis(doc, wavePanel); markAxis = new MarkerAxis (doc, wavePanel); viewMarkers = app.getUserPrefs().getBoolean(PrefsUtil.KEY_VIEWMARKERS, false); markVisible = viewMarkers && waveExpanded; markAxisHeader = new TrackRowHeader(doc.markerTrack, doc.tracks, doc.selectedTracks, doc.getUndoManager()); markAxisHeader.setPreferredSize(new Dimension( 63, markAxis.getPreferredSize().height)); // XXX markAxisHeader.setMaximumSize (new Dimension(128, markAxis.getMaximumSize ().height)); // XXX if (markVisible) { markAxis.startListening(); } else { markAxis.setVisible(false); markAxisHeader.setVisible(false); } flagsPanel = new JPanel(new StretchedGridLayout(0, 1, 1, 1)); metersPanel = new JPanel(new StretchedGridLayout(0, 1, 1, 1)); rulersPanel = new JPanel(new StretchedGridLayout(0, 1, 1, 1)); lmm.setDynamicComponent(metersPanel); JPanel waveHeaderPanel = new JPanel(new BorderLayout()); channelHeaderPanel = new JPanel(); channelHeaderPanel.setLayout(new BoxLayout(channelHeaderPanel, BoxLayout.X_AXIS)); final Box bbb = Box.createVerticalBox(); final GradientPanel gp = GUIUtil.createGradientPanel(); gp.setBottomBorder(true); gp.setLayout(null); gp.setPreferredSize(new Dimension(0, timeAxis.getPreferredSize().height)); bbb.add(gp); bbb.add(markAxisHeader); waveHeaderPanel.add(bbb, BorderLayout.NORTH); channelHeaderPanel.add(flagsPanel); channelHeaderPanel.add(metersPanel); channelHeaderPanel.add(rulersPanel); waveHeaderPanel.add(channelHeaderPanel, BorderLayout.CENTER); waveView = new WaveformView(doc, wavePanel); wavePanel.setLayout(new BoxLayout(wavePanel, BoxLayout.Y_AXIS)); wavePanel.add(timeAxis); wavePanel.add(markAxis); wavePanel.add(waveView); // fixes bug with WebLaF scroll-bar (timeline-scroll) // that will otherwise get focus despite being focus-disabled... waveView.setFocusable(true); waveView.requestFocus(); waveView.addMouseListener(new MouseAdapter() { @Override public void mousePressed(MouseEvent e) { waveView.requestFocus(); } }); scroll = new TimelineScroll(doc); JPanel ggTrackPanel = new JPanel(new BorderLayout()); ggTrackPanel.add(wavePanel, BorderLayout.CENTER); ggTrackPanel.add(waveHeaderPanel, BorderLayout.WEST); ggTrackPanel.add(scroll, BorderLayout.SOUTH); lbWriteProtected = new JLabel(); ggAudioInfo = new ModificationButton(ModificationButton.SHAPE_INFO); ggAudioInfo.setAction(new ActionAudioInfo()); ggAudioInfo.setToolTipText(getResourceString("ttAudioInfo")); ggRevealFile = new ModificationButton(ModificationButton.SHAPE_REVEAL); actionRevealFile = new ActionRevealFile(); ggRevealFile.setAction(actionRevealFile); ggRevealFile.setToolTipText(getResourceString("ttRevealFile")); ggAudioFileDescr = new JLabel(); lbSRC = new JLabel(getResourceString("buttonSRC")); lbSRC.setForeground(GraphicsUtil.colrRed()); lbSRC.setPreferredSize(lbSRC.getPreferredSize()); lbSRC.setText(null); box = Box.createHorizontalBox(); box.add(Box.createHorizontalStrut(4)); box.add(lbWriteProtected); box.add(ggAudioInfo); box.add(ggRevealFile); box.add(Box.createHorizontalStrut(4)); pProgress = new ProgressPanel(); pOverlay = new CrossfadePanel(); pOverlay.setComponentA(ggAudioFileDescr); pOverlay.setComponentB(pProgress); box.add(pOverlay); box.add(Box.createHorizontalStrut(4)); box.add(lbSRC); box.add(CoverGrowBox.create(2, 0)); updateAFDGadget(); updateCursorFormat(); // ----- afr export ----- final JButton ggExportAFR = new JButton(getResourceString("buttonDragRegion"), new ImageIcon(getClass().getResource("dragicon.png"))); ggExportAFR.setToolTipText(getResourceString("ttDragRegion")); ggExportAFR.setTransferHandler(new AFRTransferHandler()); final MouseInputAdapter expAFRmia = new MouseInputAdapter() { private MouseEvent dndInit = null; private boolean dndStarted = false; public void mousePressed(MouseEvent e) { dndInit = e; dndStarted = false; } public void mouseReleased(MouseEvent e) { dndInit = null; dndStarted = false; } public void mouseDragged(MouseEvent e) { if (!dndStarted && (dndInit != null) && ((Math.abs(e.getX() - dndInit.getX()) > 5) || (Math.abs(e.getY() - dndInit.getY()) > 5))) { JComponent c = (JComponent) e.getSource(); c.getTransferHandler().exportAsDrag(c, e, TransferHandler.COPY); dndStarted = true; } } }; ggExportAFR.addMouseListener(expAFRmia); ggExportAFR.addMouseMotionListener(expAFRmia); timeTB.add(Box.createHorizontalStrut(4)); timeTB.addButton(ggExportAFR); // ---------- topPane.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2)); timeTB.setOpaque(false); topPane.add(timeTB); transTB.setOpaque(false); topPane.add(transTB); topPane.add(Box.createHorizontalGlue()); cbr = new ComponentBoundsRestrictor(); ggTreeExp = new TreeExpanderButton(); ggTreeExp.setExpandedToolTip(getResourceString("buttonExpWaveTT")); ggTreeExp.setCollapsedToolTip(getResourceString("buttonCollWaveTT")); ggTreeExp.setExpanded(true); ggTreeExp.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { final Dimension d = getSize(); waveExpanded = ggTreeExp.isExpanded(); markVisible = viewMarkers && waveExpanded; if (waveExpanded) { cbr.remove(getWindow()); waveView.setVisible(true); channelHeaderPanel.setVisible(true); if (viewMarkers) { markAxis.setVisible(true); markAxisHeader.setVisible(true); } scroll.setVisible(true); timeTB.setVisible(true); pack(); } else { checkDecimatedTrails(); setPreferredSize(getSize()); waveView.setVisible(false); channelHeaderPanel.setVisible(false); markAxis.setVisible(false); markAxisHeader.setVisible(false); scroll.setVisible(false); timeTB.setVisible(false); actionZoomAllOut.perform(); final int h = d.height - (waveView.getHeight() + scroll.getHeight() + (viewMarkers ? markAxis.getHeight() : 0)); setSize(new Dimension(d.width - timeTB.getWidth(), h)); cbr.setMinimumHeight(h); cbr.setMaximumHeight(h); cbr.add(getWindow()); } } }); topPane.add(ggTreeExp); gp.setGradientShift(0, topPane.getPreferredSize().height); cp.add(topPane , BorderLayout.NORTH); cp.add(ggTrackPanel , BorderLayout.CENTER); cp.add(box , BorderLayout.SOUTH); // --- Tools --- pointerTool = new TimelinePointerTool(); tools.put(ToolAction.POINTER, pointerTool); tools.put(ToolAction.ZOOM, new TimelineZoomTool()); // ---- TopPainter ---- trackPainter = new TopPainter() { public void paintOnTop(Graphics2D g2) { Rectangle r; r = new Rectangle(0, 0, wavePanel.getWidth(), wavePanel.getHeight()); // getViewRect(); if (!vpRecentRect.equals(r)) { recalculateTransforms(r); } for (int i = 0; i < vpSelections.size(); i++) { r = vpSelections.get(i); g2.setColor(vpSelectionColors.get(i)); g2.fillRect(vpSelectionRect.x, r.y - vpRecentRect.y, vpSelectionRect.width, r.height); } if (markVisible) { markAxis.paintFlagSticks(g2, vpRecentRect); } g2.setColor(colrPosition); g2.drawLine(vpPosition, 0, vpPosition, vpRecentRect.height); if (vpZoomRect != null) { g2.setColor(colrZoom); g2.setStroke(vpZoomStroke[vpZoomStrokeIdx]); g2.drawRect(vpZoomRect.x, vpZoomRect.y, vpZoomRect.width, vpZoomRect.height); } } }; wavePanel.addTopPainter(trackPainter); // ---- listeners ---- doc.timeline.addTimelineListener(this); doc.audioTracks.addListener(new SessionCollection.Listener() { public void sessionCollectionChanged(SessionCollection.Event e) { documentUpdate(); } public void sessionObjectMapChanged(SessionCollection.Event e) { /* ignored */ } public void sessionObjectChanged(SessionCollection.Event e) { // nothing } }); doc.selectedTracks.addListener(new SessionCollection.Listener() { public void sessionCollectionChanged(SessionCollection.Event e) { updateSelectionAndRepaint(); } public void sessionObjectMapChanged(SessionCollection.Event e) { /* ignore */ } public void sessionObjectChanged(SessionCollection.Event e) { /* ignore */ } }); transport.addTransportListener(this); doc.markers.addListener(new Trail.Listener() { public void trailModified(Trail.Event e) { repaintMarkers(e.getAffectedSpan()); } }); doc.getAudioTrail().addListener(new Trail.Listener() { public void trailModified(Trail.Event e) { if (!waveExpanded || !e.getAffectedSpan().touches(timelineVis)) return; updateOverviews(false, false); } }); winListener = new AbstractWindow.Adapter() { public void windowClosing(AbstractWindow.Event e) { actionClose.perform(); } public void windowActivated(AbstractWindow.Event e) { // need to check 'disposed' to avoid runtime exception in doc handler if document was just closed if (!disposed) { app.getDocumentHandler().setActiveDocument(DocumentFrame.this, doc); ((BasicWindowHandler) app.getWindowHandler()).setMenuBarBorrower(DocumentFrame.this); } } }; this.addListener(winListener); waveView.addComponentListener(new ComponentAdapter() { public void componentResized(ComponentEvent e) { updateSelectionAndRepaint(); } }); timeTB.addToolActionListener(this); timeTB.selectTool(ToolAction.POINTER); playTimer = new Timer(33, new ActionListener() { public void actionPerformed(ActionEvent e) { timelinePos = transport.getCurrentFrame(); updatePositionAndRepaint(); scroll.setPosition(timelinePos, 50, TimelineScroll.TYPE_TRANSPORT); } }); // --- Actions --- actionNewFromSel = new ActionNewFromSel(); actionClose = new ActionClose(); actionSave = new ActionSave(); actionSaveAs = new ActionSaveAs(false, false); ActionSaveAs actionSaveCopyAs = new ActionSaveAs(true, false); actionSaveSelectionAs = new ActionSaveAs(true, true); ActionSelectAll actionSelectAll = new ActionSelectAll(); MenuAction actionInsertRec = new ActionInsertRec(); actionProcess = new ActionProcess(); actionProcessAgain = new ActionProcessAgain(); MenuAction actionFadeIn = new ActionPlugIn(plugInPackage + "FadeIn", false); MenuAction actionFadeInD = new ActionPlugIn(plugInPackage + "FadeIn", true ); MenuAction actionFadeOut = new ActionPlugIn(plugInPackage + "FadeOut", false); MenuAction actionFadeOutD = new ActionPlugIn(plugInPackage + "FadeOut", true); MenuAction actionGain = new ActionPlugIn(plugInPackage + "Gain"); MenuAction actionInvert = new ActionPlugIn(plugInPackage + "Invert"); MenuAction actionReverse = new ActionPlugIn(plugInPackage + "Reverse"); MenuAction actionRotateChannels = new ActionPlugIn(plugInPackage + "RotateChannels"); MenuAction actionFScNeedlehole = new ActionPlugIn(fscapePackage + "Needlehole"); MenuAction actionDebugDump = new ActionDebugDump(); MenuAction actionDebugVerify = new ActionDebugVerify(); AbstractAction actionIncVertMax = new ActionVerticalMax(2.0f, 6f); AbstractAction actionDecVertMax = new ActionVerticalMax(0.5f, -6f); AbstractAction actionIncVertMin = new ActionVerticalMin(6f); AbstractAction actionDecVertMin = new ActionVerticalMin(-6f); ActionSpanWidth actionIncHoriz = new ActionSpanWidth(2.0f); ActionSpanWidth actionDecHoriz = new ActionSpanWidth(0.5f); actionZoomAllOut = new ActionScroll(SCROLL_ENTIRE_SESSION); actionShowWindow = new ShowWindowAction(this); iMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, InputEvent.CTRL_MASK), "incvmax"); aMap.put("incvmax", actionIncVertMax); iMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, InputEvent.CTRL_MASK), "decvmax"); aMap.put("decvmax", actionDecVertMax); iMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, InputEvent.CTRL_MASK | InputEvent.ALT_MASK), "incvmin"); aMap.put("incvmin", actionIncVertMin); iMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, InputEvent.CTRL_MASK | InputEvent.ALT_MASK), "decvmin"); aMap.put("decvmin", actionDecVertMin); iMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, InputEvent.CTRL_MASK), "inch"); iMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_OPEN_BRACKET, BasicMenuFactory.MENU_SHORTCUT), "inch"); aMap.put("inch", actionIncHoriz); iMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, InputEvent.CTRL_MASK), "dech"); iMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_CLOSE_BRACKET, BasicMenuFactory.MENU_SHORTCUT), "dech"); aMap.put("dech", actionDecHoriz); iMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, myMeta), "samplvl"); aMap.put("samplvl", new ActionSpanWidth(0.0f)); iMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "retn"); aMap.put("retn", new ActionScroll(SCROLL_SESSION_START)); iMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0), "left"); aMap.put("left", new ActionScroll(SCROLL_SELECTION_START)); iMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0), "right"); aMap.put("right", new ActionScroll(SCROLL_SELECTION_STOP)); iMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_F, InputEvent.ALT_MASK), "fit"); aMap.put("fit", new ActionScroll(SCROLL_FIT_TO_SELECTION)); iMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, InputEvent.ALT_MASK), "entire"); iMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, myMeta), "entire"); aMap.put("entire", actionZoomAllOut); iMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, InputEvent.SHIFT_MASK), "seltobeg"); aMap.put("seltobeg", new ActionSelect(SELECT_TO_SESSION_START)); iMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, InputEvent.SHIFT_MASK + InputEvent.ALT_MASK), "seltoend"); aMap.put("seltoend", new ActionSelect(SELECT_TO_SESSION_END)); iMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0), "postoselbegc"); aMap.put("postoselbegc", doc.timeline.getPosToSelAction(true, true)); iMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0), "postoselendc"); aMap.put("postoselendc", doc.timeline.getPosToSelAction(false, true)); iMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, InputEvent.ALT_MASK), "postoselbeg"); aMap.put("postoselbeg", doc.timeline.getPosToSelAction(true, false)); iMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, InputEvent.ALT_MASK), "postoselend"); aMap.put("postoselend", doc.timeline.getPosToSelAction(false, false)); iMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_M, 0), "dropmark"); aMap.put("dropmark", new ActionDropMarker()); iMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0), "selnextreg"); iMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_LESS, 0), "selnextreg"); iMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_BACK_QUOTE, 0), "selnextreg"); aMap.put("selnextreg", new ActionSelectRegion(SELECT_NEXT_REGION)); iMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_TAB, InputEvent.ALT_MASK), "selprevreg"); iMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_LESS, InputEvent.CTRL_MASK), "selprevreg"); iMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_BACK_QUOTE, InputEvent.CTRL_MASK), "selprevreg"); aMap.put("selprevreg", new ActionSelectRegion(SELECT_PREV_REGION)); iMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_TAB, InputEvent.SHIFT_MASK), "extnextreg"); iMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_LESS, InputEvent.SHIFT_MASK), "extnextreg"); iMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_BACK_QUOTE, InputEvent.SHIFT_MASK), "extnextreg"); aMap.put("extnextreg", new ActionSelectRegion(EXTEND_NEXT_REGION)); iMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_TAB, InputEvent.ALT_MASK + InputEvent.SHIFT_MASK), "extprevreg"); iMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_LESS, InputEvent.CTRL_MASK + InputEvent.SHIFT_MASK), "extprevreg"); iMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_BACK_QUOTE, InputEvent.CTRL_MASK + InputEvent.SHIFT_MASK), "extprevreg"); aMap.put("extprevreg", new ActionSelectRegion(EXTEND_PREV_REGION)); setFocusTraversalKeysEnabled(false); // we want the tab! we gotta have that tab! setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); // ---- menus and actions ---- mr = app.getMenuBarRoot(); mr.putMimic("file.new.fromSelection", this, actionNewFromSel); mr.putMimic("file.close", this, actionClose); mr.putMimic("file.save", this, actionSave); mr.putMimic("file.saveAs", this, actionSaveAs); mr.putMimic("file.saveCopyAs", this, actionSaveCopyAs); mr.putMimic("file.saveSelectionAs", this, actionSaveSelectionAs); mr.putMimic("edit.undo", this, doc.getUndoManager().getUndoAction()); mr.putMimic("edit.redo", this, doc.getUndoManager().getRedoAction()); mr.putMimic("edit.cut", this, doc.getCutAction()); mr.putMimic("edit.copy", this, doc.getCopyAction()); mr.putMimic("edit.paste", this, doc.getPasteAction()); mr.putMimic("edit.clear", this, doc.getDeleteAction()); mr.putMimic("edit.selectAll", this, actionSelectAll); mr.putMimic("timeline.insertSilence", this, doc.getSilenceAction()); mr.putMimic("timeline.insertRecording", this, actionInsertRec); mr.putMimic("timeline.trimToSelection", this, doc.getTrimAction()); mr.putMimic("process.again", this, actionProcessAgain); mr.putMimic("process.fadeIn", this, actionFadeIn); mr.putMimic("process.fadeInD", this, actionFadeInD); mr.putMimic("process.fadeOut", this, actionFadeOut); mr.putMimic("process.fadeOutD", this, actionFadeOutD); mr.putMimic("process.gain", this, actionGain); mr.putMimic("process.invert", this, actionInvert); mr.putMimic("process.reverse", this, actionReverse); mr.putMimic("process.rotateChannels", this, actionRotateChannels); mr.putMimic("process.fscape.needlehole", this, actionFScNeedlehole); mr.putMimic("debug.dumpRegions", this, actionDebugDump); mr.putMimic("debug.verifyRegions", this, actionDebugVerify); updateEditEnabled(false); AbstractWindowHandler.setDeepFont(cp, Collections.singletonList(timeTB)); GUIUtil.setDeepFont(timeTB, app.getGraphicsHandler().getFont(GraphicsHandler.FONT_MINI)); app.getMenuFactory().addToWindowMenu( actionShowWindow ); // MUST BE BEFORE INIT()!! init(); updateTitle(); documentUpdate(); addDynamicListening( new DynamicPrefChangeManager( app.getUserPrefs(), new String[] { PrefsUtil.KEY_VIEWNULLLINIE, PrefsUtil.KEY_VIEWVERTICALRULERS, PrefsUtil.KEY_VIEWMARKERS, PrefsUtil.KEY_TIMEUNITS, PrefsUtil.KEY_VERTSCALE, PrefsUtil.KEY_VIEWCHANMETERS }, this )); initBounds(); // be sure this is after documentUpdate! setVisible(true); toFront(); } public void setLoop(boolean onOff) { transTB.setLoop(onOff); } protected boolean alwaysPackSize() { return false; } private void initBounds() { final Preferences cp = getClassPrefs(); final BasicWindowHandler bwh = getWindowHandler(); final Rectangle sr = bwh.getWindowSpace(); final Dimension dt = stringToDimension( cp.get( KEY_TRACKSIZE, null )); final Dimension d = dt == null ? new Dimension() : dt; final float hf = (float) Math.sqrt( Math.max( 1, waveView.getNumChannels() )); final Dimension winSize; final Rectangle wr; int w = d.width; int h = d.height; sr.x += 36; sr.y += 36; sr.width -= 60; sr.height -= 60; if( w <= 0 ) { w = sr.width*2/3 - AudioTrackRowHeader.ROW_WIDTH; } if( h <= 0 ) { h = (sr.height - 106) / 4; // 106 = approx. extra space for title bar, tool bar etc. } waveView.setPreferredSize(new Dimension(w, (int) (h * hf + 0.5f))); pack(); winSize = getSize(); wr = new Rectangle(lastLeftTop.x + 21, lastLeftTop.y + 23, winSize.width, winSize.height); GUIUtil.wrapWindowBounds(wr, sr); lastLeftTop.setLocation(wr.getLocation()); setBounds(wr); waveView.addComponentListener(new ComponentAdapter() { public void componentResized(ComponentEvent e) { if (waveExpanded) { final Dimension dNew = e.getComponent().getSize(); dNew.height = (int) (dNew.height / hf + 0.5f); if (!dNew.equals(d)) { d.setSize(dNew); cp.put(KEY_TRACKSIZE, AppWindow.dimensionToString(dNew)); } } } }); } protected void checkDecimatedTrails() { final DecimatedTrail dt; if (waveExpanded) { if (verticalScale == PrefsUtil.VSCALE_FREQ_SPECT) { if (doc.getDecimatedSonaTrail() == null) { try { final DecimatedSonaTrail dst = doc.createDecimatedSonaTrail(); // set initial freq bounds of waveview waveView.setFreqMinMax(dst.getMinFreq(), dst.getMaxFreq()); } catch (IOException e1) { e1.printStackTrace(); } } dt = doc.getDecimatedSonaTrail(); } else { if (doc.getDecimatedWaveTrail() == null) { try { doc.createDecimatedWaveTrail(); } catch (IOException e1) { e1.printStackTrace(); } } dt = doc.getDecimatedWaveTrail(); } if (dt != asyncTrail) { if (asyncTrail != null) asyncTrail.removeAsyncListener(this); asyncTrail = dt; if (asyncTrail != null) asyncTrail.addAsyncListener(this); } } } public void addCatchBypass() { scroll.addCatchBypass(); } public void removeCatchBypass() { scroll.removeCatchBypass(); } public void repaintMarkers(Span affectedSpan) { if (!markVisible || !affectedSpan.touches(timelineVis)) return; final Span span = affectedSpan.shift(-timelineVis.start); final Rectangle updateRect = new Rectangle( (int) (span.start * vpScale), 0, (int) (span.getLength() * vpScale) + 2, wavePanel.getHeight()). intersection(new Rectangle(0, 0, wavePanel.getWidth(), wavePanel.getHeight())); if (!updateRect.isEmpty()) { // update markAxis in any case, even if it's invisible // coz otherwise the flag stakes are not updated! wavePanel.update(markAxis); wavePanel.repaint(updateRect); } } public void playerCreated(SuperColliderPlayer p) { lmm.setInputs(p.getInputBus()); lmm.addTaskSync(p.getInputSync()); } public void playerDestroyed(SuperColliderPlayer p) { lmm.clearInputs(); } public void setSRCEnabled(boolean onOff) { lbSRC.setText(onOff ? getResourceString("buttonSRC") : null); } public void setForceMeters(boolean onOff) { if (onOff != forceMeters) { forceMeters = onOff; showHideMeters(); final int holdDur = forceMeters ? -1 : PeakMeter.DEFAULT_HOLD_DUR; for (PeakMeter channelMeter : channelMeters) { channelMeter.setHoldDuration(holdDur); } } } public float getMaxMeterHold() { float hold = Float.NEGATIVE_INFINITY; for (PeakMeter channelMeter : channelMeters) { hold = Math.max(hold, channelMeter.getHoldDecibels()); } return hold; } public void clearMeterHold() { for (PeakMeter channelMeter : channelMeters) { channelMeter.clearHold(); } } private void showHideMeters() { final boolean visible = chanMeters || forceMeters; if (metersPanel.isVisible() != visible) { metersPanel.setVisible(visible); } } /** * Recreates the main frame's title bar * after a sessions name changed (clear/load/save as session) */ public void updateTitle() { // final File fDisp = doc.getDisplayDescr().file; final AudioFileDescr[] afds = doc.getDescr(); final String name; final Icon icn; final File f; writeProtected = false; f = afds.length == 0 ? null : afds[0].file; actionRevealFile.setFile(f); if( doc.getName() == null ) { name = getResourceString( "frameUntitled" ); } else { name = doc.getName(); try { for (AudioFileDescr afd : afds) { final File f1 = afd.file; if (f1 == null) continue; writeProtected |= !f1.canWrite() || ((f1.getParentFile() != null) && !f1.getParentFile().canWrite()); } } catch( SecurityException e ) { /* ignored */ } } if( writeProtected ) { icn = GUIUtil.getNoWriteIcon(); if( lbWriteProtected.getIcon() != icn ) { lbWriteProtected.setIcon( icn ); } } else if( lbWriteProtected.getIcon() != null ) { lbWriteProtected.setIcon( null ); } // icnWriteProtected.setID( writeProtected ? MutableIcon.WRITE_PROTECTED : MutableIcon.INVISIBLE ); // lbWriteProtected.repaint(); if( internalFrames ) { if( doc.isDirty() ) { setTitle( "\u2022" + name ); } else { setTitle( name ); } } else { setTitle( app.getName() + (doc.isDirty() ? " - \u2022" : " - " ) + name ); final Component c = getComponent(); if (c instanceof JFrame) { final JFrame jf = (JFrame) c; jf.getRootPane().putClientProperty("Window.documentFile", f); } } actionShowWindow.putValue( Action.NAME, name ); actionSave.setEnabled( !writeProtected && doc.isDirty() ); setDirty( doc.isDirty() ); final AudioFileInfoPalette infoBox = (AudioFileInfoPalette) app.getComponent( Main.COMP_AUDIOINFO ); if( infoBox != null ) infoBox.updateDocumentName( doc ); if( writeProtected && !wpHaveWarned && doc.isDirty() ) { // MutableIcon warnIcon = new MutableIcon( 128 ); // warnIcon.setID( MutableIcon.WRITE_PROTECTED ); final JOptionPane op = new JOptionPane( getResourceString( "warnWriteProtected" ), JOptionPane.WARNING_MESSAGE ); // JOptionPane.showMessageDialog( getWindow(), getResourceString( "warnWriteProtected" ), // getResourceString( "msgDlgWarn" ), JOptionPane.WARNING_MESSAGE, null ); BasicWindowHandler.showDialog( op, getWindow(), getResourceString( "msgDlgWarn" )); wpHaveWarned = true; } } // sync: attempts exclusive on MTE and shared on TIME! protected void updateOverviews(boolean justBecauseOfResize, boolean allTracks) { waveView.update(timelineVis); if (allTracks) wavePanel.updateAll(); } protected String getResourceString(String key) { return app.getResourceString(key); } protected void documentUpdate() { final List<PeakMeter> collChannelMeters; PeakMeter[] meters; AudioTrackRowHeader chanHead; AudioTrack t; int oldChannels, newChannels; Axis chanRuler; PeakMeter chanMeter; newChannels = doc.getDisplayDescr().channels; oldChannels = collChannelHeaders.size(); meters = channelMeters; collChannelMeters = new ArrayList<PeakMeter>( meters.length ); Collections.addAll(collChannelMeters, meters); // first kick out editors whose tracks have been removed for( int ch = 0; ch < oldChannels; ch++ ) { chanHead = collChannelHeaders.get( ch ); t = (AudioTrack) chanHead.getTrack(); if( !doc.audioTracks.contains( t )) { chanHead = collChannelHeaders.remove( ch ); chanMeter = collChannelMeters.remove( ch ); chanRuler = collChannelRulers.remove( ch ); oldChannels--; // XXX : dispose trnsEdit (e.g. free vectors, remove listeners!!) flagsPanel.remove( chanHead ); metersPanel.remove( chanMeter ); rulersPanel.remove( chanRuler ); ch--; chanHead.dispose(); chanMeter.dispose(); chanRuler.dispose(); } } // next look for newly added transmitters and create editors for them newLp: for( int ch = 0; ch < newChannels; ch++ ) { t = (AudioTrack) doc.audioTracks.get( ch ); for( int ch2 = 0; ch2 < oldChannels; ch2++ ) { chanHead = collChannelHeaders.get( ch ); if( chanHead.getTrack() == t ) continue newLp; } chanHead = new AudioTrackRowHeader( t, doc.tracks, doc.selectedTracks, doc.getUndoManager() ); collChannelHeaders.add( chanHead ); flagsPanel.add( chanHead, ch ); chanMeter = new PeakMeter(); collChannelMeters.add( chanMeter ); metersPanel.add( chanMeter, ch ); chanRuler = new Axis( Axis.VERTICAL, Axis.FIXEDBOUNDS ); collChannelRulers.add( chanRuler ); rulersPanel.add( chanRuler, ch ); } meters = new PeakMeter[ collChannelMeters.size() ]; for( int ch = 0; ch < meters.length; ch++ ) { meters[ ch ] = collChannelMeters.get( ch ); } channelMeters = meters; lmm.setView( new PeakMeterGroup( meters )); updateOverviews( false, true ); } // private void initStrip( Axis chanRuler, PeakMeter chanMeter ) // { // final Preferences prefs = app.getUserPrefs(); // // chanMeter.setVisible( prefs.getBoolean( PrefsUtil.KEY_VIEWCHANMETERS, false )); // chanRuler.setVisible( prefs.getBoolean( PrefsUtil.KEY_VIEWVERTICALRULERS, false )); // } public ProcessingThread closeDocument( boolean force, Flag wasClosed ) { doc.getTransport().stop(); if( !force ) { final String name = getResourceString( "menuClose" ); if( !confirmCancel( name )) { wasClosed.set( false ); return null; } final ProcessingThread pt = confirmUnsaved( name, wasClosed ); if( pt != null ) { pt.addListener( new ProcessingThread.Listener() { public void processStarted( ProcessingThread.Event e ) { /* ignored */ } public void processStopped( ProcessingThread.Event e ) { if( e.isDone() ) { documentClosed(); } } }); return pt; } } if( wasClosed.isSet() ) { documentClosed(); } return null; } protected void documentClosed() { disposed = true; // important to avoid "too late window messages" to be processed; fucking swing doesn't kill them despite listener being removed //System.err.println( "DocumentFrame.documentClosed()" ); this.removeListener( winListener ); actionShowWindow.dispose(); // this.removeWindowFocusListener( winListener ); // otherwise we'll try to set an obsolete active doc app.getDocumentHandler().removeDocument( this, doc ); // invokes doc.dispose() and hence this.dispose() // actionShowWindow.removeAll(); } public void dispose() { playTimer.stop(); app.getMenuFactory().removeFromWindowMenu( actionShowWindow ); AudioTrackRowHeader chanHead; Axis chanRuler; lmm.dispose(); wavePanel.dispose(); while( !collChannelHeaders.isEmpty() ) { chanHead = collChannelHeaders.remove( 0 ); chanHead.dispose(); } while( !collChannelRulers.isEmpty() ) { chanRuler = collChannelRulers.remove( 0 ); chanRuler.dispose(); } for (PeakMeter channelMeter : channelMeters) { channelMeter.dispose(); } channelMeters = new PeakMeter[ 0 ]; markAxis.stopListening(); markAxis.dispose(); timeAxis.dispose(); timeTB.dispose(); transTB.dispose(); super.dispose(); } private void updateEditEnabled( boolean enabled ) { Action ma; ma = doc.getCutAction(); if( ma != null ) ma.setEnabled( enabled ); ma = doc.getCopyAction(); if( ma != null ) ma.setEnabled( enabled ); ma = doc.getDeleteAction(); if( ma != null ) ma.setEnabled( enabled ); ma = doc.getTrimAction(); if( ma != null ) ma.setEnabled( enabled ); actionProcess.setEnabled( enabled ); actionNewFromSel.setEnabled( enabled ); actionSaveSelectionAs.setEnabled( enabled ); } @SuppressWarnings("serial") private static class SetFlagAndDisposeAction extends AbstractAction { private final Flag flag; private final JDialog d; public SetFlagAndDisposeAction(Flag flag, JDialog d) { super(); this.flag = flag; this.d = d; } public void actionPerformed(ActionEvent e) { flag.set(true); d.dispose(); } } /* * Checks if there are unsaved changes to * the session. If so, displays a confirmation * dialog. Invokes Save/Save As depending * on user selection. IF the doc was not dirty, * or if "Cancel" or * "Don't save" was chosen, the * method returns <code>null</code> and the * <code>confirmed</code> flag reflects whether * the document should be closed. If a saving * process should be started, that process is * returned. Note that the <code>ProcessingThread</code> * in this case has not yet been started, as to * allow interested objects to install a listener * first. So it's their job to call the <code>start</code> * method! * * @param actionName name of the action that * threatens the session * @param confirmed a flag that will be set to <code>true</code> if * the doc is allowed to be closed * (doc was not dirty or user chose "Don't save"), * otherwise <code>false</code> (save process * initiated or user chose "Cancel"). * @return a saving process yet to be started or <code>null</code> * if the doc needn't/shouldn't be saved * * @see de.sciss.eisenkraut.util.ProcessingThread#start */ private ProcessingThread confirmUnsaved( String actionName, Flag confirmed ) { if( !doc.isDirty() ) { confirmed.set( true ); return null; } final Object[] options = { getResourceString( "buttonSave" ), getResourceString( "buttonCancel" ), getResourceString( "buttonDontSave" )}; final int choice; // final AudioFileDescr displayAFD = doc.getDisplayDescr(); final String name; final JOptionPane op; final JDialog d; final JRootPane rp; final Flag dont = new Flag( false ); AudioFileDescr[] afds = doc.getDescr(); if( doc.getName() == null ) { name = getResourceString( "frameUntitled" ); } else { name = doc.getName(); } // choice = JOptionPane.showOptionDialog( getWindow(), name + " :\n" + getResourceString( "optionDlgUnsaved" ), // actionName, JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE, null, // options, options[1] ); op = new JOptionPane( name + " :\n" + getResourceString( "optionDlgUnsaved" ), JOptionPane.WARNING_MESSAGE, JOptionPane.YES_NO_CANCEL_OPTION, null, options, options[ 1 ]); d = op.createDialog( getWindow(), actionName ); rp = d.getRootPane(); if( rp != null ) { rp.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put( KeyStroke.getKeyStroke(KeyEvent.VK_D, BasicMenuFactory.MENU_SHORTCUT), "dont"); rp.getActionMap().put("dont", new SetFlagAndDisposeAction(dont, d)); } BasicWindowHandler.showDialog(d); if (dont.isSet()) { choice = 2; } else { final Object value = op.getValue(); if ((value == null) || (value == options[1])) { choice = 1; } else if (value == options[0]) { choice = 0; } else if (value == options[2]) { choice = 2; } else { choice = -1; // throws assertion error in switch block } } switch( choice ) { case JOptionPane.CLOSED_OPTION: case 1: // cancel confirmed.set( false ); return null; case 2: // don't save confirmed.set( true ); return null; case 0: confirmed.set( false ); if( (doc.getDisplayDescr().file == null) || writeProtected ) { afds = actionSaveAs.query( afds ); } if( afds != null ) { return actionSave.initiate( actionSave.getValue( Action.NAME ).toString(), null, afds, null, true, false, false ); } return null; default: assert false : choice; return null; } } private boolean confirmCancel( String actionName ) { if( doc.checkProcess( 50 )) { return true; } final int choice; // final AudioFileDescr displayAFD = doc.getDisplayDescr(); final String name; if( doc.getName() == null ) { name = getResourceString( "frameUntitled" ); } else { name = doc.getName(); } final JOptionPane op = new JOptionPane( name + " :\n" + getResourceString( "optionDlgProcessing" ) + "\n(" + doc.getProcessName() + ")?", JOptionPane.WARNING_MESSAGE, JOptionPane.YES_NO_OPTION ); // choice = JOptionPane.showConfirmDialog( getWindow(), name + " :\n" + getResourceString( "optionDlgProcessing" ) + // "\n(" + doc.getProcessName() + ")?", // actionName, JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE ); choice = BasicWindowHandler.showDialog( op, getWindow(), actionName ); switch( choice ) { case JOptionPane.CLOSED_OPTION: case JOptionPane.NO_OPTION: return false; case JOptionPane.YES_OPTION: // abort doc.cancelProcess( true ); return true; default: assert false : choice; return false; } } /** * Only call in the Swing thread! */ protected void updatePositionAndRepaint() { boolean pEmpty, cEmpty; int x, x2; pEmpty = (vpPositionRect.x + vpPositionRect.width < 0) || (vpPositionRect.x > vpRecentRect.width); if( !pEmpty ) vpUpdateRect.setBounds( vpPositionRect ); if( vpScale > 0f ) { vpPosition = (int) ((timelinePos - timelineVis.getStart()) * vpScale + 0.5f); // choose update rect such that even a paint manager delay of 200 milliseconds // will still catch the (then advanced) position so we don't see flickering! // XXX this should take playback rate into account, though vpPositionRect.setBounds( vpPosition, 0, Math.max( 1, (int) (vpScale * timelineRate * 0.2f) ), vpRecentRect.height ); } else { vpPosition = -1; vpPositionRect.setBounds( 0, 0, 0, 0 ); } cEmpty = (vpPositionRect.x + vpPositionRect.width <= 0) || (vpPositionRect.x > vpRecentRect.width); if( pEmpty ) { if( cEmpty ) return; x = Math.max( 0, vpPositionRect.x ); x2 = Math.min( vpRecentRect.width, vpPositionRect.x + vpPositionRect.width ); vpUpdateRect.setBounds( x, vpPositionRect.y, x2 - x, vpPositionRect.height ); } else { if( cEmpty ) { x = Math.max( 0, vpUpdateRect.x ); x2 = Math.min( vpRecentRect.width, vpUpdateRect.x + vpUpdateRect.width ); vpUpdateRect.setBounds( x, vpUpdateRect.y, x2 - x, vpUpdateRect.height ); } else { x = Math.max( 0, Math.min( vpUpdateRect.x, vpPositionRect.x )); x2 = Math.min( vpRecentRect.width, Math.max( vpUpdateRect.x + vpUpdateRect.width, vpPositionRect.x + vpPositionRect.width )); vpUpdateRect.setBounds( x, vpUpdateRect.y, x2 - x, vpUpdateRect.height ); } } if( !vpUpdateRect.isEmpty() ) { wavePanel.repaint( vpUpdateRect ); } } /** * Only call in the Swing thread! */ protected void updateSelectionAndRepaint() { final Rectangle r = new Rectangle(0, 0, wavePanel.getWidth(), wavePanel.getHeight()); vpUpdateRect.setBounds(vpSelectionRect); recalculateTransforms(r); updateSelection(); if (vpUpdateRect.isEmpty()) { vpUpdateRect.setBounds(vpSelectionRect); } else if (!vpSelectionRect.isEmpty()) { vpUpdateRect = vpUpdateRect.union(vpSelectionRect); } vpUpdateRect = vpUpdateRect.intersection(new Rectangle(0, 0, wavePanel.getWidth(), wavePanel.getHeight())); if (!vpUpdateRect.isEmpty()) { wavePanel.repaint(vpUpdateRect); } } /** * Only call in the Swing thread! */ private void updateTransformsAndRepaint(boolean verticalSelection) { final Rectangle r = new Rectangle(0, 0, wavePanel.getWidth(), wavePanel.getHeight()); vpUpdateRect = vpSelectionRect.union(vpPositionRect); recalculateTransforms(r); if (verticalSelection) updateSelection(); vpUpdateRect = vpUpdateRect.union(vpPositionRect).union(vpSelectionRect).intersection(r); if (!vpUpdateRect.isEmpty()) { wavePanel.repaint(vpUpdateRect); // XXX ?? } } protected void recalculateTransforms(Rectangle newRect) { int x, w; vpRecentRect = newRect; // getViewRect(); if (!timelineVis.isEmpty()) { vpScale = (float) vpRecentRect.width / (float) timelineVis.getLength(); // - 1; playTimer.setDelay(Math.min((int) (1000 / (vpScale * timelineRate * playRate)), 33)); vpPosition = (int) ((timelinePos - timelineVis.getStart()) * vpScale + 0.5f); vpPositionRect.setBounds(vpPosition, 0, 1, vpRecentRect.height); if (!timelineSel.isEmpty()) { x = (int) ((timelineSel.getStart() - timelineVis.getStart()) * vpScale + 0.5f) + vpRecentRect.x; w = Math.max(1, (int) ((timelineSel.getStop() - timelineVis.getStart()) * vpScale + 0.5f) - x); vpSelectionRect.setBounds(x, 0, w, vpRecentRect.height); } else { vpSelectionRect.setBounds(0, 0, 0, 0); } } else { vpScale = 0.0f; vpPosition = -1; vpPositionRect .setBounds(0, 0, 0, 0); vpSelectionRect.setBounds(0, 0, 0, 0); } } // sync: caller must sync on timeline + grp + tc private void updateSelection() { Rectangle r; Track t; int x, y; vpSelections.clear(); vpSelectionColors.clear(); if (!timelineSel.isEmpty()) { x = waveView.getX(); y = waveView.getY(); vpSelections.add(timeAxis.getBounds()); vpSelectionColors.add(colrSelection); t = doc.markerTrack; vpSelections.add(markAxis.getBounds()); vpSelectionColors.add(doc.selectedTracks.contains(t) ? colrSelection : colrSelection2); for (int ch = 0; ch < waveView.getNumChannels(); ch++) { r = new Rectangle(waveView.rectForChannel(ch)); r.translate(x, y); t = (Track) doc.audioTracks.get(ch); vpSelections.add(r); vpSelectionColors.add(doc.selectedTracks.contains(t) ? colrSelection : colrSelection2); } } } protected void setZoomRect( Rectangle r ) { vpZoomRect = r; vpZoomStrokeIdx = (vpZoomStrokeIdx + 1) % vpZoomStroke.length; wavePanel.repaint(); } // ------------- DecimatedTrail.AsyncListener interface ------------- public void asyncFinished( DecimatedTrail.AsyncEvent e ) { final DecimatedTrail dt = e.getDecimatedTrail(); dt.removeAsyncListener( this ); if( dt == asyncTrail ) asyncTrail = null; updateOverviews( false, true ); } public void asyncUpdate( DecimatedTrail.AsyncEvent e ) { updateOverviews( false, true ); } // ---------------- ProgressComponent interface ---------------- public void addCancelListener( ActionListener l ) { pProgress.addCancelListener( l ); } public void removeCancelListener( ActionListener l ) { pProgress.removeCancelListener( l ); } public Component getComponent() { return getWindow(); } public void resetProgression() { pProgress.resetProgression(); pOverlay.performFade( 1f, 1000, 250 ); } public void setProgression( float p ) { pProgress.setProgression( p ); } public void finishProgression( int result ) { if( result != CANCELLED ) pProgress.finishProgression( result ); pOverlay.performFade( 0f, result == CANCELLED ? 0 : 4000, 250 ); } public void setProgressionText( String text ) { pProgress.setProgressionText( text ); } public void showMessage( int type, String text ) { pProgress.showMessage( type, text ); } public void displayError(Exception e, String processName) { BasicWindowHandler.showErrorDialog(getWindow(), e, processName); } protected void updateAFDGadget() { final AudioFileDescr displayAFD = doc.getDisplayDescr(); final AudioFileDescr[] afds = doc.getDescr(); displayAFD.rate = timelineRate; displayAFD.length = timelineLen; for (AudioFileDescr afd : afds) { afd.rate = displayAFD.rate; afd.length = displayAFD.length; } ggAudioFileDescr.setText(displayAFD.getFormat()); pOverlay.performFade(0f, 1000, 250); } protected void updateCursorFormat() { final AudioFileDescr displayAFD = doc.getDisplayDescr(); csrInfoBits = displayAFD.bitsPerSample; csrInfoIsInt = displayAFD.sampleFormat == AudioFileDescr.FORMAT_INT; } protected void updateVerticalRuler() { final VectorSpace spc; final float min, max; Axis chanRuler; switch( waveView.getVerticalScale() ) { case PrefsUtil.VSCALE_AMP_LIN: min = waveView.getAmpLinMin() * 100; max = waveView.getAmpLinMax() * 100; spc = VectorSpace.createLinSpace( 0.0, 1.0, min, max, null, null, null, null ); break; case PrefsUtil.VSCALE_AMP_LOG: min = waveView.getAmpLogMin(); max = waveView.getAmpLogMax(); spc = VectorSpace.createLinSpace( 0.0, 1.0, min, max, null, null, null, null ); break; case PrefsUtil.VSCALE_FREQ_SPECT: min = waveView.getFreqMin(); max = waveView.getFreqMax(); spc = VectorSpace.createLinLogSpace( 0.0, 1.0, min, max, Math.sqrt( min * max ), null, null, null, null ); break; default: assert false : waveView.getVerticalScale(); spc = null; } for (Axis collChannelRuler : collChannelRulers) { chanRuler = collChannelRuler; chanRuler.setSpace(spc); } } // ---------------- TimelineListener interface ---------------- public void timelineSelected( TimelineEvent e ) { final boolean wasEmpty = timelineSel.isEmpty(); final boolean isEmpty; timelineSel = doc.timeline.getSelectionSpan(); updateSelectionAndRepaint(); isEmpty = timelineSel.isEmpty(); if( wasEmpty != isEmpty ) { updateEditEnabled( !isEmpty ); } } // warning : don't call doc.setAudioFileDescr, it will restore the old markers! public void timelineChanged( TimelineEvent e ) { timelineRate = doc.timeline.getRate(); timelineLen = doc.timeline.getLength(); playTimer.setDelay( Math.min( (int) (1000 / (vpScale * timelineRate * playRate)), 33 )); updateAFDGadget(); updateOverviews( false, true ); } public void timelinePositioned( TimelineEvent e ) { timelinePos = doc.timeline.getPosition(); updatePositionAndRepaint(); scroll.setPosition( timelinePos, 0, pointerTool.validDrag ? TimelineScroll.TYPE_DRAG : TimelineScroll.TYPE_UNKNOWN ); } public void timelineScrolled( TimelineEvent e ) { //System.out.println( "scrolled " + doc.timeline.getVisibleSpan() ); timelineVis = doc.timeline.getVisibleSpan(); updateOverviews( false, true ); updateTransformsAndRepaint( false ); } // ---------------- TransportListener interface ---------------- public void transportPlay( Transport t, long pos, double rate ) { playRate = rate; playTimer.setDelay( Math.min( (int) (1000 / (vpScale * timelineRate * playRate)), 33 )); playTimer.restart(); } public void transportStop( Transport t, long pos ) { playTimer.stop(); } public void transportPosition( Transport t, long pos, double rate ) { /* ignored */ } public void transportReadjust( Transport t, long pos, double rate ) { /* ignored */ } public void transportQuit( Transport t ) { playTimer.stop(); } // ---------------- RealtimeConsumer interface ---------------- // /** // * Requests 30 fps notification (no data block requests). // * This is used to update the timeline position during transport // * playback. // */ // public RealtimeConsumerRequest createRequest( RealtimeContext context ) // { // RealtimeConsumerRequest request = new RealtimeConsumerRequest( this, context ); // // 30 fps is visually fluent // request.notifyTickStep = RealtimeConsumerRequest.approximateStep( context, 30 ); // request.notifyTicks = true; // request.notifyOffhand = true; // return request; // } // // public void realtimeTick( RealtimeContext context, long timelinePos ) // { // this.timelinePos = timelinePos; //updatePositionAndRepaint(); // scroll.setPosition( timelinePos, 50, TimelineScroll.TYPE_TRANSPORT ); // } // // public void offhandTick( RealtimeContext context, long timelinePos ) // { // } // ---------------- ToolListener interface ---------------- // sync: attemptShared DOOR_TRNS public void toolChanged(ToolActionEvent e) { if (activeTool != null) { activeTool.toolDismissed(waveView); } activeTool = tools.get(e.getToolAction().getID()); if (activeTool != null) { waveView.setCursor(e.getToolAction().getDefaultCursor()); activeTool.toolAcquired(waveView); } else { waveView.setCursor(null); } } // ---------------- PreferenceChangeListener interface ---------------- public void preferenceChange(PreferenceChangeEvent e) { final String key = e.getKey(); if (key.equals(PrefsUtil.KEY_VIEWNULLLINIE)) { waveView.setNullLine(e.getNode().getBoolean(e.getKey(), false)); } else if (key.equals(PrefsUtil.KEY_VIEWVERTICALRULERS)) { final boolean visible = e.getNode().getBoolean(e.getKey(), false); rulersPanel.setVisible(visible); } else if (key.equals(PrefsUtil.KEY_VIEWCHANMETERS)) { chanMeters = e.getNode().getBoolean(e.getKey(), false); showHideMeters(); } else if (key.equals(PrefsUtil.KEY_VIEWMARKERS)) { viewMarkers = e.getNode().getBoolean(e.getKey(), false); markVisible = viewMarkers && waveExpanded; if (waveExpanded) { markAxis.setVisible(markVisible); markAxisHeader.setVisible(markVisible); wavePanel.updateAll(); } if (markVisible) { markAxis.startListening(); } else { markAxis.stopListening(); } } else if (key.equals(PrefsUtil.KEY_TIMEUNITS)) { final boolean timeSmps = e.getNode().getInt(key, PrefsUtil.TIME_SAMPLES) == PrefsUtil.TIME_SAMPLES; msgCsr1.applyPattern(timeSmps ? smpPtrn : timePtrn); } else if (key.equals(PrefsUtil.KEY_VERTSCALE)) { verticalScale = e.getNode().getInt(key, PrefsUtil.VSCALE_AMP_LIN); checkDecimatedTrails(); // needs to be before setVert.scale / updateRuler! waveView.setVerticalScale(verticalScale); updateVerticalRuler(); } } // ---------------- ClipboardOwner interface ---------------- public void lostOwnership(Clipboard clipboard, Transferable contents) { // XXX possibly call dispose() } // ---------------- internal action classes ---------------- @SuppressWarnings("serial") private class ActionDebugDump extends MenuAction { protected ActionDebugDump() { /* empty */ } public void actionPerformed( ActionEvent e ) { System.err.println( "------------ Document: "+doc.getDisplayDescr().file+" ------------" ); doc.getAudioTrail().debugDump(); System.err.println( " --------- decimated ---------" ); doc.getDecimatedWaveTrail().debugDump(); } } @SuppressWarnings("serial") private class ActionDebugVerify extends MenuAction { protected ActionDebugVerify() { /* empty */ } public void actionPerformed( ActionEvent e ) { System.err.println( "------------ Document: " + doc.getDisplayDescr().file + " ------------" ); doc.getAudioTrail().debugVerifyContiguity(); System.err.println(" --------- decimated ---------"); doc.getDecimatedWaveTrail().debugVerifyContiguity(); } } @SuppressWarnings("serial") private class ActionNewFromSel extends MenuAction { protected ActionNewFromSel() { /* empty */ } public void actionPerformed( ActionEvent e ) { final ClipboardTrackList tl = doc.getSelectionAsTrackList(); final Session doc2; final AudioFileDescr afd, afd2; final int selChans; final ProcessingThread pt; if( tl == null ) return; selChans = tl.getTrackNum( AudioTrail.class ); afd = doc.getDisplayDescr(); afd2 = new AudioFileDescr(); afd2.bitsPerSample = afd.bitsPerSample; afd2.channels = selChans; afd2.rate = afd.rate; afd2.sampleFormat = afd.sampleFormat; doc2 = ((MenuFactory) app.getMenuFactory()).newDocument( afd2 ); if( doc2 == null ) { // it's important that the clipboard tl be diposed // when not used any more tl.dispose(); return; } pt = doc2.pasteTrackList( tl, 0, getResourceString( "menuPaste" ), Session.EDIT_INSERT ); if( pt != null ) { pt.addListener( new ProcessingThread.Listener() { public void processStarted( ProcessingThread.Event e1 ) { /* ignored */ } public void processStopped( ProcessingThread.Event e2 ) { tl.dispose(); } }); doc.start( pt ); } else { tl.dispose(); } } } // actionNewFromSelClass // action for the Save-Session menu item @SuppressWarnings("serial") private class ActionClose extends MenuAction { protected ActionClose() { /* empty */ } public void actionPerformed( ActionEvent e ) { perform(); } public void perform() { final ProcessingThread pt = closeDocument( false, new Flag( false )); if( pt != null ) doc.start( pt ); } } // action for the Save-Session menu item @SuppressWarnings("serial") private class ActionSave extends MenuAction { protected ActionSave() { /* empty */ } /** * Saves a Session. If the file * wasn't saved before, a file chooser * is shown before. */ public void actionPerformed( ActionEvent e ) { final AudioFileDescr displayAFD = doc.getDisplayDescr(); final AudioFileDescr[] afds; if( displayAFD.file == null ) { afds = actionSaveAs.query( doc.getDescr() ); } else { afds = doc.getDescr(); } if( afds != null ) { perform( getValue( NAME ).toString(), afds ); } } protected void perform( String name, AudioFileDescr[] afds ) { perform( name, null, afds, null, true, false, false ); } protected void perform( String name, Span span, AudioFileDescr[] afds, int[] channelMap, boolean saveMarkers, boolean asCopy, boolean openAfterSave ) { final ProcessingThread pt = initiate( name, span, afds, channelMap, saveMarkers, asCopy, openAfterSave ); if( pt != null ) doc.start( pt ); } protected ProcessingThread initiate( String name, final Span span, final AudioFileDescr[] afds, int[] channelMap, boolean saveMarkers, final boolean asCopy, final boolean openAfterSave ) { final ProcessingThread pt = doc.procSave( name, span, afds, channelMap, saveMarkers, asCopy ); if( pt == null ) return null; pt.addListener( new ProcessingThread.Listener() { public void processStopped( ProcessingThread.Event e ) { if( !e.isDone() ) return; wpHaveWarned = false; if( !asCopy ) { if( afds.length == 1 ) app.getMenuFactory().addRecent( afds[ 0 ].file ); updateAFDGadget(); updateCursorFormat(); } if( openAfterSave ) { if( afds.length == 1 ) { app.getMenuFactory().openDocument( afds[ 0 ].file ); } else { final File[] fs = new File[ afds.length ]; for( int i = 0; i < afds.length; i++ ) { fs[ i ] = afds[ i ].file; } ((MenuFactory) app.getMenuFactory()).openDocument( fs ); } } } public void processStarted( ProcessingThread.Event e ) { /* ignored */ } }); return pt; } } // action for the Save-Session-As menu item @SuppressWarnings("serial") private class ActionSaveAs extends MenuAction { private final boolean asCopy; private final boolean selection; private final Flag openAfterSave; protected ActionSaveAs( boolean asCopy, boolean selection ) { if( selection && !asCopy ) throw new IllegalArgumentException(); this.asCopy = asCopy; this.selection = selection; openAfterSave = new Flag( false ); } /* * Query a file name from the user and save the Session */ public void actionPerformed( ActionEvent e ) { final List<Track.Info> infos = Track.getInfos( doc.selectedTracks.getAll(), doc.tracks.getAll() ); boolean saveMarkers = true; int[] channelMap = null; if( selection ) { for (Object info : infos) { Track.Info ti = (Track.Info) info; if (ti.trail instanceof AudioTrail) { int numSelChannels = 0; for (int j = 0; j < ti.trackMap.length; j++) { if (ti.trackMap[j]) numSelChannels++; } channelMap = new int[numSelChannels]; for (int j = 0, k = 0; j < ti.trackMap.length; j++) { if (ti.trackMap[j]) channelMap[k++] = j; } } else if (ti.trail instanceof MarkerTrail) { saveMarkers = ti.selected; } } } final AudioFileDescr[] afds = query( doc.getDescr(), channelMap, saveMarkers, asCopy, selection, openAfterSave ); if( afds != null ) { actionSave.perform( getValue( NAME ).toString(), selection ? timelineSel : null, afds, channelMap, saveMarkers, asCopy, openAfterSave.isSet() ); } } protected AudioFileDescr[] query( AudioFileDescr[] protoType ) { return query( protoType, null, true, false, false, null ); } /** * Open a file chooser so the user * can select a new output file and format for the session. * * @return the AudioFileDescr representing the chosen file path * and format or <code>null</code> * if the dialog was cancelled. */ protected AudioFileDescr[] query( AudioFileDescr[] protoType, int[] channelMap, boolean saveMarkers, boolean asCopySettings, boolean selectionSettings, Flag openAfterSaveSettings ) { if( protoType.length == 0 ) return null; // final FileDialog fDlg; final AudioFileDescr[] afds; final AudioFileFormatPane affp; // final JOptionPane dlg; final SpringPanel msgPane; final PathField[] ggPathFields; final int[] channelsUsed = new int[ protoType.length ]; final JCheckBox ggOpenAfterSave; final String prefsDirKey = selectionSettings ? PrefsUtil.KEY_FILESAVESELDIR : PrefsUtil.KEY_FILESAVEDIR; final JPanel p; int filesUsed = 0; File f; // , f2; String[] queryOptions = { getResourceString( "buttonSave" ), getResourceString( "buttonCancel" )}; int i, result; String str; JLabel lb; String fileName, dirName; boolean setFocus = false; int y = 0; //System.out.print( "channelMap = [ " ); //for( int kkk = 0; kkk < channelMap.length; kkk++ ) System.out.print( (kkk > 0 ? ", " : "") + channelMap[ kkk ]); //System.out.println( " ]" ); msgPane = new SpringPanel( 4, 2, 4, 2 ); ggPathFields = new PathField[ protoType.length ]; affp = new AudioFileFormatPane( AudioFileFormatPane.FORMAT | AudioFileFormatPane.ENCODING ); affp.fromDescr( protoType[0] ); lb = new JLabel( getResourceString( "labelOutputFile" ), RIGHT ); // lb.setLabelFor( ggPathField ); msgPane.gridAdd( lb, 0, y ); for( int j = 0, chanOff = 0; j < protoType.length; chanOff += protoType[ j ].channels, j++, y++ ) { if( channelMap == null ) { channelsUsed[ j ] = protoType[ j ].channels; } else { for (int aChannelMap : channelMap) { if ((aChannelMap >= chanOff) && (aChannelMap < chanOff + protoType[j].channels)) { channelsUsed[j]++; } } } //System.out.println( "channelsUsed[ " + j + " ] = " + channelsUsed[ j ]); if( channelsUsed[ j ] == 0 ) continue; filesUsed++; ggPathFields[ j ] = new PathField( PathField.TYPE_OUTPUTFILE, getValue( NAME ).toString() ); if( protoType[ j ].file == null ) { fileName = getResourceString( "frameUntitled" ) + (ggPathFields.length > 1 ? "-" + (j+1) : ""); } else if( asCopySettings || selectionSettings ) { str = protoType[ j ].file.getName(); i = str.lastIndexOf( '.' ); if( i == -1 ) i = str.length(); fileName = str.substring( 0, i ) + (selectionSettings ? getResourceString( "fileDlgCut" ) : " " + getResourceString( "fileDlgCopy" )); // suffix is appended by affp! } else { fileName = protoType[ j ].file.getName(); } dirName = app.getUserPrefs().get( prefsDirKey, protoType[ j ].file == null ? System.getProperty( "user.home" ) : protoType[ j ].file.getParent() ); ggPathFields[ j ].setPath( new File( dirName, fileName )); affp.automaticFileSuffix( ggPathFields[ j ] ); if( (protoType[ j ].file == null) || asCopySettings || selectionSettings ) { // create non-existent file name ggPathFields[ j ].setPath( IOUtil.nonExistentFileVariant( ggPathFields[ j ].getPath(), -1, selectionSettings ? null : " ", null )); } ggPathFields[ j ].selectFileName( false ); msgPane.gridAdd( ggPathFields[ j ], 1, y ); if( !setFocus ) { GUIUtil.setInitialDialogFocus( ggPathFields[ j ]); setFocus = true; } } lb = new JLabel( getResourceString( "labelFormat" ), RIGHT ); msgPane.gridAdd( lb, 0, y ); msgPane.gridAdd( affp, 1, y, -1, 1 ); lb.setLabelFor( affp ); y++; if( asCopySettings ) { ggOpenAfterSave = new JCheckBox( getResourceString( "labelOpenAfterSave" )); ggOpenAfterSave.setSelected( openAfterSaveSettings.isSet() ); msgPane.gridAdd( ggOpenAfterSave, 1, y ); } else { ggOpenAfterSave = null; // msgPane.gridAdd( new JLabel( " " ), 1, y ); } // AbstractWindowHandler.setDeepFont( msgPane ); msgPane.makeCompactGrid(); p = new JPanel(new BorderLayout()); p.add(msgPane, BorderLayout.NORTH); final JOptionPane op = new JOptionPane( p, JOptionPane.QUESTION_MESSAGE, JOptionPane.YES_NO_OPTION, null, queryOptions, queryOptions[ 0 ]); // result = JOptionPane.showOptionDialog( getWindow(), p, getValue( NAME ).toString(), // JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, // null, queryOptions, queryOptions[ 0 ]); result = BasicWindowHandler.showDialog( op, getWindow(), getValue( NAME ).toString() ); if( ggOpenAfterSave != null ) { openAfterSaveSettings.set( ggOpenAfterSave.isSelected() ); } if( result == 0 ) { // save dir prefs if( ggPathFields.length > 0 ) { app.getUserPrefs().put( prefsDirKey, ggPathFields[ 0 ].getPath().getParent() ); } afds = new AudioFileDescr[ filesUsed ]; for( int j = 0, k = 0; j < ggPathFields.length; j++ ) { if( channelsUsed[ j ] == 0 ) continue; f = ggPathFields[ j ].getPath(); if( f.exists() ) { queryOptions = new String[] { getResourceString( "buttonOverwrite" ), getResourceString( "buttonCancel" )}; final JOptionPane op2 = new JOptionPane( getResourceString( "warnFileExists" ) + ":\n" + f.getAbsolutePath() + "\n" + getResourceString( "warnOverwriteFile" ), JOptionPane.WARNING_MESSAGE, JOptionPane.YES_NO_OPTION, null, queryOptions, queryOptions[1] ); // result = JOptionPane.showOptionDialog( getWindow(), getResourceString( "warnFileExists" ) + // ":\n" + f.getAbsolutePath() + "\n" + getResourceString( "warnOverwriteFile" ), // getValue( NAME ).toString(), JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE, // null, queryOptions, queryOptions[1] ); result = BasicWindowHandler.showDialog( op2, getWindow(), getValue( NAME ).toString() ); if( result != 0 ) return null; } afds[ k ] = new AudioFileDescr( protoType[ j ]); affp.toDescr( afds[ k ]); afds[ k ].file = f; afds[ k ].channels = channelsUsed[ j ]; k++; } return afds; } else { return null; } } } // private class actionImportMarkersClass // extends MenuAction // { // public void actionPerformed( ActionEvent e ) // { // new ImportMarkersDialog( doc ); // } // } @SuppressWarnings("serial") private class ActionSelectAll extends MenuAction { protected ActionSelectAll() { /* empty */ } public void actionPerformed( ActionEvent e ) { doc.timeline.editSelect( this, new Span( 0, timelineLen )); } } @SuppressWarnings("serial") private class ActionInsertRec extends MenuAction { protected ActionInsertRec() { /* empty */ } public void actionPerformed( ActionEvent e ) { final RecorderDialog recDlg; final File recFile; final Session tmpDoc; final ClipboardTrackList tl; final ProcessingThread pt; final Session targetDoc; final AudioFileDescr afd, afd2; try { recDlg = new RecorderDialog( doc ); } catch( IOException e1 ) { BasicWindowHandler.showErrorDialog( getWindow(), e1, getValue( NAME ).toString() ); return; } recFile = recDlg.getResult(); if( recFile != null ) { try { // if( true ) throw new IOException( "test" ); tmpDoc = Session.newFrom( recFile, false, false ); } catch( IOException e1 ) { GUIUtil.displayError( getWindow(), e1, getValue( NAME ).toString() ); confirmDelete( recFile ); return; } tl = new ClipboardTrackList( tmpDoc, new Span( 0, tmpDoc.timeline.getLength() ), tmpDoc.tracks.getAll() ); if( doc.checkProcess()) { targetDoc = doc; } else { // doc busy, save the recording by creating a separate document afd = doc.getDisplayDescr(); afd2 = new AudioFileDescr(); afd2.bitsPerSample = afd.bitsPerSample; afd2.channels = afd.channels; afd2.rate = afd.rate; afd2.sampleFormat = afd.sampleFormat; targetDoc = ((MenuFactory) app.getMenuFactory()).newDocument( afd2 ); } //pt = null; pt = targetDoc == null ? null : targetDoc.pasteTrackList( tl, targetDoc.timeline.getPosition(), getValue( NAME ).toString(), targetDoc.getEditMode() ); if( pt != null ) { pt.addListener( new ProcessingThread.Listener() { public void processStarted( ProcessingThread.Event e1 ) { /* ignored */ } public void processStopped( ProcessingThread.Event e2 ) { tl.dispose(); tmpDoc.dispose(); if( e2.isDone() ) { deleteFile( recFile ); } else { confirmDelete( recFile ); } } }); targetDoc.start( pt ); } else { tl.dispose(); tmpDoc.dispose(); confirmDelete( recFile ); } } } protected void confirmDelete(File path) { final int choice; final Object[] options = new String[]{getResourceString("buttonKeepFile"), getResourceString("buttonDeleteFile")}; final JOptionPane op = new JOptionPane(getResourceString("optionDlgKeepRec1") + path.getAbsolutePath() + getResourceString("optionDlgKeepRec2"), JOptionPane.ERROR_MESSAGE, JOptionPane.YES_NO_OPTION, null, options, options[0]); choice = BasicWindowHandler.showDialog(op, getWindow(), getValue(NAME).toString()); if (choice == 1) { deleteFile(path); } } protected void deleteFile(File path) { if (!path.delete()) { final JOptionPane op = new JOptionPane(path.getAbsolutePath() + ":\n" + getResourceString("errDeleteFile"), JOptionPane.WARNING_MESSAGE); BasicWindowHandler.showDialog(op, getWindow(), getValue(NAME).toString()); } } } // class actionInsertRecClass @SuppressWarnings("serial") private class ActionProcess extends MenuAction { protected ActionProcess() { /* empty */ } public void actionPerformed( ActionEvent e ) { /* empty */ } } @SuppressWarnings("serial") private class ActionPlugIn extends MenuAction { private final String plugInClassName; private final boolean forceDisplay; protected ActionPlugIn(String plugInClassName) { this(plugInClassName, false); } protected ActionPlugIn(String plugInClassName, boolean forceDisplay) { this.plugInClassName = plugInClassName; this.forceDisplay = forceDisplay; } public void actionPerformed(ActionEvent e) { FilterDialog filterDlg = (FilterDialog) app.getComponent(Main.COMP_FILTER); // System.out.println("MOD -- " + e.getModifiers()); // System.out.println("SRC -- " + e.getSource()); if (filterDlg == null) { filterDlg = new FilterDialog(); } filterDlg.process(plugInClassName, doc, forceDisplay /* (e.getModifiers() & ActionEvent.SHIFT_MASK) != 0 */, false); actionProcessAgain.setPlugIn(filterDlg.getPlugIn()); } } @SuppressWarnings("serial") private class ActionProcessAgain extends MenuAction { private String plugInClassName = null; protected ActionProcessAgain() { super(); setEnabled( false ); } public void actionPerformed( ActionEvent e ) { if( plugInClassName == null ) return; FilterDialog filterDlg = (FilterDialog) app.getComponent( Main.COMP_FILTER ); if( filterDlg == null ) { filterDlg = new FilterDialog(); } filterDlg.process( plugInClassName, doc, false, true ); } protected void setPlugIn( RenderPlugIn plugIn ) { if( plugIn == null ) { if( isEnabled() ) { setEnabled( false ); putValue( NAME, getResourceString( "menuProcessAgain" )); } plugInClassName = null; } else { if( !isEnabled() ) { setEnabled( true ); } putValue( NAME, getResourceString( "menuProcessAgain" ) + " : " + plugIn.getName() ); plugInClassName = plugIn.getClass().getName(); } } } @SuppressWarnings("serial") private class ActionAudioInfo extends MenuAction { protected ActionAudioInfo() { /* empty */ } /** * Brings up the Audio-Info-Box */ public void actionPerformed( ActionEvent e ) { AudioFileInfoPalette infoBox = (AudioFileInfoPalette) app.getComponent( Main.COMP_AUDIOINFO ); if( infoBox == null ) { infoBox = new AudioFileInfoPalette(); } infoBox.setVisible( true ); infoBox.toFront(); } } @SuppressWarnings("serial") private class ActionRevealFile extends MenuAction { private File f; protected ActionRevealFile() { super("Reveal File in Finder"); setFile(null); } private void splitAndAdd(String cmd, int i, int j, List<String> list) { final String[] pre = cmd.substring(i, j).split(" "); for (String s : pre) { final String t = s.trim(); if (!t.isEmpty()) list.add(t); } } /** * Shows the file in the desktop manager (Mac and Linux only) */ public void actionPerformed(ActionEvent e) { if (f == null) return; // if (Main.isMac ) performMac (); // else if (Main.isLinux) performLinux(); final String cmd = app.getUserPrefs().get(PrefsUtil.KEY_REVEAL_FILE, null); if (cmd == null) return; final List<String> cmdList = new ArrayList<String>(); int i = 0; while (i < cmd.length()) { final int j = cmd.indexOf('\'', i); final int k = cmd.indexOf('\'', j + 1); if (j < k) { splitAndAdd(cmd, i, j, cmdList); final String s = cmd.substring(j + 1, k); cmdList.add(s); i = k + 1; } else { splitAndAdd(cmd, i, cmd.length(), cmdList); i = cmd.length(); } } try { boolean hasEscape = false; for (int j = 0; j < cmdList.size(); j++) { final String s = cmdList.get(j); if (s.startsWith("%")) { hasEscape = true; if (s.length() > 2) throw new IOException("Reveal Command - illegal placeholder " + s); final char tpe = s.length() == 1 ? 'p' : s.charAt(1); final boolean isURL = Character.isUpperCase(tpe); final String plain; switch(Character.toLowerCase(tpe)) { case 'p': plain = f.getPath(); break; case 'd': plain = f.getParent(); break; case 'f': plain = f.getName(); break; default: throw new IOException("Reveal Command - illegal placeholder " + s); } final String rep = isURL ? mkURL(plain) : plain; cmdList.set(j, rep); } } if (!hasEscape) cmdList.add(f.getPath()); final String[] cmdArray = new String[cmdList.size()]; cmdList.toArray(cmdArray); Runtime.getRuntime().exec(cmdArray, null, null); } catch (IOException e1) { displayError(e1, getValue(NAME).toString()); } } // osascript -e 'tell application "Finder"' -e 'activate' -e 'open location "file:///Volumes/Claude/audio"' // -e 'select file "Sine441HzGain.aif" of folder of the front window' // -e 'end tell' private String mkURL(String in) throws MalformedURLException, UnsupportedEncodingException { final File f = new File(in); String path = f.toURI().toURL().toExternalForm(); path = path.substring(5); StringBuilder sb = new StringBuilder(); int chI; byte[] hex = "0123456789abcdef".getBytes(); byte[] enc = path.getBytes("UTF-8"); for (byte anEnc : enc) { chI = anEnc; // parentDir.charAt( i ); if ((chI < 33) || (chI > 127)) { sb.append("%").append((char) hex[(chI >> 4) & 0x0F]).append((char) hex[chI & 0x0F]); } else { sb.append((char) chI); } } path = sb.toString(); return path; } // private void performMac() { // try { // // make sure space characters are escaped as %20 in URL style // String path = f.getParentFile().getAbsoluteFile().toURI().toURL().toExternalForm(); // path = path.substring(5); // StringBuilder sb = new StringBuilder(); // int chI; // byte[] hex = "0123456789abcdef".getBytes(); // byte[] enc = path.getBytes("UTF-8"); // for (byte anEnc : enc) { // chI = anEnc; // parentDir.charAt( i ); // if ((chI < 33) || (chI > 127)) { // sb.append("%").append((char) hex[(chI >> 4) & 0x0F]).append((char) hex[chI & 0x0F]); // } else { // sb.append((char) chI); // } // } // path = sb.toString(); // final String parentDir = path; // final String fileName = f.getName(); // final String[] cmdArray = {"osascript", "-e", "tell application \"Finder\"", "-e", "activate", // "-e", "open location \"file://" + parentDir + "\"", // "-e", "select file \"" + fileName + "\" of folder of the front window", // "-e", "end tell"}; // Runtime.getRuntime().exec(cmdArray, null, null); // } catch (IOException e1) { // displayError(e1, getValue(NAME).toString()); // } // } protected void setFile(File f) { this.f = f; setEnabled(f != null); } } // private abstract class ActionVerticalZoom // extends AbstractAction // { // protected ActionVerticalZoom() { /* empty */ } // } /** * Increase or decrease the vertical * range of the waveform display */ @SuppressWarnings("serial") private class ActionVerticalMax extends AbstractAction { private final float linFactor; private final float logOffset; /** * @param linFactor factors > 1 increase the row height, * factors < 1 decrease. */ protected ActionVerticalMax(float linFactor, float logOffset) { super(); this.linFactor = linFactor; this.logOffset = logOffset; } public void actionPerformed(ActionEvent e) { if (waveView.getVerticalScale() == PrefsUtil.VSCALE_AMP_LIN) zoomLin(); else zoomLog(); } private void zoomLin() { float min, max; min = waveView.getAmpLinMin(); max = waveView.getAmpLinMax(); if( ((linFactor >= 1.0f) && (min > -1.0e6f) && (max < 1.0e6f)) || (linFactor < 1.0f && (min < -1.0e-4f) && (max > 1.0e-4f)) ) { min *= linFactor; max *= linFactor; waveView.setAmpLinMinMax( min, max ); updateVerticalRuler(); } } private void zoomLog() { float min, max; min = waveView.getAmpLogMin(); max = waveView.getAmpLogMax(); if( (max + logOffset - min >= 6f) && (((logOffset >= 0f) && (max < 60)) || (logOffset < 0f && (max > -160))) ) { // min += logOffset; max += logOffset; waveView.setAmpLogMinMax( min, max ); updateVerticalRuler(); } } } // class actionVerticalMax /** * Increase or decrease the vertical * noisefloor of the waveform display (in log mode) */ @SuppressWarnings("serial") private class ActionVerticalMin extends AbstractAction { private final float logOffset; /** */ protected ActionVerticalMin( float logOffset ) { super(); this.logOffset = logOffset; } public void actionPerformed( ActionEvent e ) { if( waveView.getVerticalScale() != PrefsUtil.VSCALE_AMP_LIN ) zoomLog(); } private void zoomLog() { float min, max; min = waveView.getAmpLogMin(); max = waveView.getAmpLogMax(); if( (max - (min + logOffset) >= 6f) && (((logOffset >= 0f) && (min < 60)) || (logOffset < 0f && (max > -160))) ) { min += logOffset; // max += logOffset; waveView.setAmpLogMinMax( min, max ); updateVerticalRuler(); } } } // class actionVerticalMin /** * Increase or decrease the width * of the visible time span */ @SuppressWarnings("serial") private class ActionSpanWidth extends AbstractAction { private final float factor; /** * @param factor factors > 1 increase the span width (zoom out) * factors < 1 decrease (zoom in). * special value 0.0 means zoom to sample level */ protected ActionSpanWidth( float factor ) { super(); this.factor = factor; } public void actionPerformed( ActionEvent e ) { perform(); } public void perform() { long pos, visiLen, start, stop; Span visiSpan; visiSpan = timelineVis; visiLen = visiSpan.getLength(); pos = timelinePos; // doc.timeline.getPosition(); if( factor == 0.0f ) { // to sample level start = Math.max( 0, pos - (wavePanel.getWidth() >> 1) ); stop = Math.min( timelineLen, start + wavePanel.getWidth() ); } else if( factor < 1.0f ) { // zoom in if( visiLen < 4 ) return; // if timeline pos visible -> try to keep it's relative position constant if( visiSpan.contains( pos )) { start = pos - (long) ((pos - visiSpan.getStart()) * factor + 0.5f); stop = start + (long) (visiLen * factor + 0.5f); // if timeline pos before visible span, zoom left hand } else if( visiSpan.getStart() > pos ) { start = visiSpan.getStart(); stop = start + (long) (visiLen * factor + 0.5f); // if timeline pos after visible span, zoom right hand } else { stop = visiSpan.getStop(); start = stop - (long) (visiLen * factor + 0.5f); } } else { // zoom out start = Math.max( 0, visiSpan.getStart() - (long) (visiLen * factor/4 + 0.5f) ); stop = Math.min( timelineLen, start + (long) (visiLen * factor + 0.5f) ); } visiSpan = new Span( start, stop ); if( !visiSpan.isEmpty() ) { doc.timeline.editScroll( this, visiSpan ); } } } // class actionSpanWidthClass private static final int SCROLL_SESSION_START = 0; private static final int SCROLL_SELECTION_START = 1; private static final int SCROLL_SELECTION_STOP = 2; private static final int SCROLL_FIT_TO_SELECTION= 3; private static final int SCROLL_ENTIRE_SESSION = 4; @SuppressWarnings("serial") private class ActionScroll extends AbstractAction { private final int mode; protected ActionScroll( int mode ) { super(); this.mode = mode; } public void actionPerformed( ActionEvent e ) { perform(); } public void perform() { UndoableEdit edit = null; Span selSpan, newSpan; long start, stop; if( mode == SCROLL_SESSION_START && transport.isRunning() ) { transport.stop(); } selSpan = timelineSel; // doc.timeline.getSelectionSpan(); switch( mode ) { case SCROLL_SESSION_START: if( timelinePos != 0 ) { edit = TimelineVisualEdit.position( this, doc, 0 ).perform(); if( !timelineVis.contains( 0 )) { final CompoundEdit ce = new BasicCompoundEdit(); ce.addEdit( edit ); newSpan = new Span( 0, timelineVis.getLength() ); ce.addEdit( TimelineVisualEdit.scroll( this, doc, newSpan ).perform() ); ce.end(); edit = ce; } } break; case SCROLL_SELECTION_START: if( selSpan.isEmpty() ) selSpan = new Span( timelinePos, timelinePos ); if( timelineVis.contains( selSpan.getStart() )) { start = Math.max( 0, selSpan.getStart() - (timelineVis.getLength() >> 1) ); } else { start = Math.max( 0, selSpan.getStart() - (timelineVis.getLength() >> 3) ); } stop = Math.min( timelineLen, start + timelineVis.getLength() ); newSpan = new Span( start, stop ); if( !timelineVis.equals( newSpan ) && !newSpan.isEmpty() ) { edit = TimelineVisualEdit.scroll( this, doc, newSpan ).perform(); } break; case SCROLL_SELECTION_STOP: if( selSpan.isEmpty() ) selSpan = new Span( timelinePos, timelinePos ); if( timelineVis.contains( selSpan.getStop() )) { stop = Math.min( timelineLen, selSpan.getStop() + (timelineVis.getLength() >> 1) ); } else { stop = Math.min( timelineLen, selSpan.getStop() + (timelineVis.getLength() >> 3) ); } start = Math.max( 0, stop - timelineVis.getLength() ); newSpan = new Span( start, stop ); if( !timelineVis.equals( newSpan ) && !newSpan.isEmpty() ) { edit = TimelineVisualEdit.scroll( this, doc, newSpan ).perform(); } break; case SCROLL_FIT_TO_SELECTION: newSpan = selSpan; if( !timelineVis.equals( newSpan ) && !newSpan.isEmpty() ) { edit = TimelineVisualEdit.scroll( this, doc, newSpan ).perform(); } break; case SCROLL_ENTIRE_SESSION: newSpan = new Span( 0, timelineLen ); if( !timelineVis.equals( newSpan ) && !newSpan.isEmpty() ) { edit = TimelineVisualEdit.scroll( this, doc, newSpan ).perform(); } break; default: assert false : mode; break; } if( edit != null ) doc.getUndoManager().addEdit( edit ); } } // class actionScrollClass private static final int SELECT_TO_SESSION_START = 0; private static final int SELECT_TO_SESSION_END = 1; @SuppressWarnings("serial") private class ActionSelect extends AbstractAction { private final int mode; protected ActionSelect( int mode ) { super(); this.mode = mode; } public void actionPerformed( ActionEvent e ) { Span selSpan, newSpan = null; selSpan = timelineSel; // doc.timeline.getSelectionSpan(); if( selSpan.isEmpty() ) { selSpan = new Span( timelinePos, timelinePos ); } switch( mode ) { case SELECT_TO_SESSION_START: if( selSpan.getStop() > 0 ){ newSpan = new Span( 0, selSpan.getStop() ); } break; case SELECT_TO_SESSION_END: if( selSpan.getStart() < timelineLen ){ newSpan = new Span( selSpan.getStart(), timelineLen ); } break; default: assert false : mode; break; } if( newSpan != null && !newSpan.equals( selSpan )) { doc.timeline.editSelect( this, newSpan ); // doc.getUndoManager().addEdit( TimelineVisualEdit.select( this, doc, newSpan )); } } } // class actionSelectClass private static final int SELECT_NEXT_REGION = 0; private static final int SELECT_PREV_REGION = 1; private static final int EXTEND_NEXT_REGION = 2; private static final int EXTEND_PREV_REGION = 3; @SuppressWarnings("serial") private class ActionSelectRegion extends AbstractAction { private final int mode; protected ActionSelectRegion( int mode ) { super(); this.mode = mode; } public void actionPerformed( ActionEvent e ) { Span selSpan; UndoableEdit edit; long start, stop; Marker mark; int idx; if( !markVisible ) return; selSpan = timelineSel; // doc.timeline.getSelectionSpan(); if( selSpan.isEmpty() ) selSpan = new Span( timelinePos, timelinePos ); start = selSpan.getStart(); stop = selSpan.getStop(); switch( mode ) { case SELECT_NEXT_REGION: case EXTEND_NEXT_REGION: idx = doc.markers.indexOf( stop + 1 ); // XXX check if( idx < 0 ) idx = -(idx + 1); if( idx == doc.markers.getNumStakes() ) { stop = timelineLen; } else { mark = doc.markers.get( idx ); stop = mark.pos; } // (-(insertion point) - 1) if( mode == SELECT_NEXT_REGION ) { idx = doc.markers.indexOf( stop - 1 ); // XXX check if( idx < 0 ) idx = -(idx + 2); if( idx == -1 ) { start = 0; } else { mark = doc.markers.get( idx ); start = mark.pos; } } break; case SELECT_PREV_REGION: case EXTEND_PREV_REGION: idx = doc.markers.indexOf( start - 1 ); // XXX check if( idx < 0 ) idx = -(idx + 2); if( idx == -1 ) { start = 0; } else { mark = doc.markers.get( idx ); start = mark.pos; } if( mode == SELECT_PREV_REGION ) { idx = doc.markers.indexOf( start + 1 ); // XXX check if( idx < 0 ) idx = -(idx + 1); if( idx == doc.markers.getNumStakes() ) { stop = timelineLen; } else { mark = doc.markers.get( idx ); stop = mark.pos; } } break; default: assert false : mode; break; } if( (start == selSpan.getStart()) && (stop == selSpan.getStop()) ) return; edit = TimelineVisualEdit.select( this, doc, new Span( start, stop )).perform(); doc.getUndoManager().addEdit( edit ); } } // class actionSelectRegionClass @SuppressWarnings("serial") private class ActionDropMarker extends AbstractAction { protected ActionDropMarker() { /* empty */ } public void actionPerformed( ActionEvent e ) { if( markVisible ) { markAxis.addMarker( timelinePos ); } } } // class actionDropMarkerClass // -------------- AFR Transfer Handler -------------- @SuppressWarnings("serial") private class AFRTransferHandler extends TransferHandler { protected AFRTransferHandler() { /* empty */ } public int getSourceActions( JComponent c ) { return COPY; } protected Transferable createTransferable( JComponent c ) { // return new StringSelection( doc.getAudioFileDescr().file.getAbsolutePath() + File.pathSeparator + // doc.timeline.getSelectionSpan().getStart() + File.pathSeparator + // doc.timeline.getSelectionSpan().getStop() ); return new StringSelection( doc.getDisplayDescr().file.getAbsolutePath() + File.pathSeparator + doc.timeline.getSelectionSpan().getStart() + File.pathSeparator + doc.timeline.getSelectionSpan().getStop() ); // System.err.println( "createTransferable" ); // return new AudioFileRegion( new File( "haschimoto" ), new Span( 42, 43 )); // // final Span span; // final AudioTrail mte; // // if( !doc.bird.attemptShared( Session.DOOR_TIME | Session.DOOR_MTE, 250 )) return null; // try { // span = timelineSel; // doc.timeline.getSelectionSpan(); // if( span.isEmpty() ) return null; // // mte = doc.getAudioTrail(); // return mte.getSampledChunkList( span ); // } // finally { // doc.bird.releaseShared( Session.DOOR_TIME | Session.DOOR_MTE ); // } } protected void exportDone( JComponent source, Transferable data, int action ) { /* ignored */ } // public boolean canImport( JComponent c, DataFlavor[] flavors ) // { // return false; // } } private abstract class TimelineTool extends AbstractTool { private final List<Component> collObservedComponents = new ArrayList<Component>(); private boolean adjustCatchBypass = false; protected TimelineTool() { /* empty */ } public void toolAcquired( Component c ) { super.toolAcquired( c ); if( c instanceof Container ) addMouseListeners( (Container) c ); } // additionally installs mouse input listeners on child components private void addMouseListeners(Container c) { Component c2; for (int i = 0; i < c.getComponentCount(); i++) { c2 = c.getComponent(i); collObservedComponents.add(c2); c2.addMouseListener(this); c2.addMouseMotionListener(this); if (c2 instanceof Container) addMouseListeners((Container) c2); // recurse } } // additionally removes mouse input listeners from child components private void removeMouseListeners() { Component c; while (!collObservedComponents.isEmpty()) { c = collObservedComponents.remove(0); c.removeMouseListener(this); c.removeMouseMotionListener(this); } } public void toolDismissed(Component c) { super.toolDismissed(c); removeMouseListeners(); if (adjustCatchBypass) { adjustCatchBypass = false; removeCatchBypass(); } } public void mousePressed( MouseEvent e ) { adjustCatchBypass = true; addCatchBypass(); super.mousePressed( e ); } public void mouseReleased( MouseEvent e ) { adjustCatchBypass = false; removeCatchBypass(); super.mouseReleased( e ); } } /* * Keyboard modifiers are consistent with Bias Peak: * Shift+Click = extend selection, Meta+Click = select all, * Alt+Drag = drag timeline position; double-click = Play */ private class TimelinePointerTool extends TimelineTool { private boolean shiftDrag, ctrlDrag, dragStarted = false; protected boolean validDrag = false; private long startPos; private int startX; private final Object[] argsCsr = new Object[8]; private final String[] csrInfo = new String[3]; protected TimelinePointerTool() { /* empty */ } public void paintOnTop( Graphics2D g ) { // not necessary } protected void cancelGesture() { dragStarted = false; validDrag = false; } public void mousePressed( MouseEvent e ) { super.mousePressed( e ); if( e.isMetaDown() ) { selectRegion( e ); dragStarted = false; validDrag = false; } else { shiftDrag = e.isShiftDown(); ctrlDrag = e.isControlDown(); dragStarted = false; validDrag = true; startX = e.getX(); processDrag( e, false ); } } public void mouseDragged( MouseEvent e ) { final ObserverPalette observer; super.mouseDragged( e ); if( validDrag ) { if( !dragStarted ) { if( shiftDrag || ctrlDrag || Math.abs( e.getX() - startX ) > 2 ) { dragStarted = true; } else return; } processDrag( e, true ); } // cursor information observer = (ObserverPalette) app.getComponent( Main.COMP_OBSERVER ); if( (observer != null) && observer.isVisible() && (observer.getShownTab() == ObserverPalette.CURSOR_TAB) ) { showCursorInfo( SwingUtilities.convertPoint( e.getComponent(), e.getPoint(), waveView )); } } private void showCursorInfo( Point screenPt ) { final ObserverPalette observer; final int ch = waveView.channelForPoint( screenPt ); if( ch == -1 ) return; final DecimationInfo info = waveView.getDecimationInfo(); if( info == null ) return; final long pos = timelineVis.getStart() + (long) ((double) screenPt.x / (double) waveView.getWidth() * timelineVis.getLength()); if( (pos < 0) || (pos >= timelineLen) ) return; final String chName = doc.audioTracks.get( ch ).getName(); final double seconds = pos / timelineRate; final AudioTrail at; final DecimatedWaveTrail dt; final float[][] data; final float[] frame; float f1; argsCsr[3] = chName; argsCsr[0] = pos; argsCsr[1] = (int) (seconds / 60); argsCsr[2] = (float) (seconds % 60); csrInfo[0] = msgCsr1.format( argsCsr ); switch( info.model ) { case DecimatedTrail.MODEL_PCM: at = doc.getAudioTrail(); data = new float[ at.getChannelNum() ][]; data[ ch ] = new float[ 1 ]; try { at.readFrames( data, 0, new Span( pos, pos + 1 )); } catch( IOException e1 ) { return; } f1 = data[ ch ][ 0 ]; argsCsr[4] = f1; argsCsr[5] = (float) (Math.log(Math.abs(f1)) * TWENTYDIVLOG10); csrInfo[1] = msgCsr2PCMFloat.format(argsCsr); if (csrInfoIsInt) { argsCsr[6] = (long) (f1 * (1L << (csrInfoBits - 1))); argsCsr[7] = csrInfoBits; csrInfo[2] = msgCsr3PCMInt.format(argsCsr); } else { csrInfo[2] = ""; } break; case DecimatedTrail.MODEL_FULLWAVE_PEAKRMS: dt = doc.getDecimatedWaveTrail(); if( dt == null ) return; frame = new float[ dt.getNumModelChannels() ]; try { dt.readFrame( Math.min( dt.getNumDecimations() - 1, info.idx + 1 ), pos, ch, frame ); } catch( IOException e1 ) { return; } f1 = Math.max(frame[0], -frame[1]); // peak pos/neg argsCsr[4] = f1; argsCsr[5] = (float) (Math.log(f1) * TWENTYDIVLOG10); f1 = (float) Math.sqrt(frame[2]); // mean sqr pos/neg argsCsr[6] = f1; argsCsr[7] = (float) (Math.log(f1) * TWENTYDIVLOG10); csrInfo[1] = msgCsr2Peak.format(argsCsr); csrInfo[2] = msgCsr3RMS.format(argsCsr); break; default: return; } observer = (ObserverPalette) app.getComponent(Main.COMP_OBSERVER); if (observer != null) observer.showCursorInfo(csrInfo); } private void selectRegion(MouseEvent e) { final Point pt = SwingUtilities.convertPoint(e.getComponent(), e.getPoint(), wavePanel); Span span, span2; long pos, start, stop; UndoableEdit edit; int idx; Marker mark; span = timelineVis; // doc.timeline.getVisibleSpan(); span2 = timelineSel; // doc.timeline.getSelectionSpan(); pos = span.getStart() + (long) (pt.getX() / getComponent().getWidth() * span.getLength()); pos = Math.max( 0, Math.min( timelineLen, pos )); stop = timelineLen; start = 0; if( markVisible ) { idx = doc.markers.indexOf( pos + 1 ); // XXX check if( idx < 0 ) idx = -(idx + 1); if( idx < doc.markers.getNumStakes() ) { mark = doc.markers.get( idx ); stop = mark.pos; } idx = doc.markers.indexOf( stop - 1 ); // XXX check if( idx < 0 ) idx = -(idx + 2); if( idx >= 0 ) { mark = doc.markers.get( idx ); start = mark.pos; } } // union with current selection if (e.isShiftDown() && !span2.isEmpty()) { start = Math.min(start, span2.start); stop = Math.max(stop, span2.stop); } span = new Span(start, stop); if (span.equals(span2)) { span = new Span(0, timelineLen); } if (!span.equals(span2)) { edit = TimelineVisualEdit.select(this, doc, span).perform(); doc.getUndoManager().addEdit(edit); } } private void processDrag(MouseEvent e, boolean hasStarted) { final Point pt = SwingUtilities.convertPoint(e.getComponent(), e.getPoint(), wavePanel); Span span, span2; long position; UndoableEdit edit; span = timelineVis; // doc.timeline.getVisibleSpan(); span2 = timelineSel; // doc.timeline.getSelectionSpan(); position = span.getStart() + (long) (pt.getX() / getComponent().getWidth() * span.getLength()); position = Math.max( 0, Math.min( timelineLen, position )); if( !hasStarted && !ctrlDrag ) { if( shiftDrag ) { if( span2.isEmpty() ) { span2 = new Span( timelinePos, timelinePos ); } startPos = Math.abs( span2.getStart() - position ) > Math.abs( span2.getStop() - position ) ? span2.getStart() : span2.getStop(); span2 = new Span( Math.min( startPos, position ), Math.max( startPos, position )); edit = TimelineVisualEdit.select( this, doc, span2 ).perform(); } else { startPos = position; if( span2.isEmpty() ) { edit = TimelineVisualEdit.position( this, doc, position ).perform(); } else { edit = new CompoundEdit(); edit.addEdit( TimelineVisualEdit.select( this, doc, new Span() ).perform() ); edit.addEdit( TimelineVisualEdit.position( this, doc, position ).perform() ); ((CompoundEdit) edit).end(); } } } else { if( ctrlDrag ) { edit = TimelineVisualEdit.position( this, doc, position ).perform(); } else { span2 = new Span( Math.min( startPos, position ), Math.max( startPos, position )); edit = TimelineVisualEdit.select( this, doc, span2 ).perform(); } } doc.getUndoManager().addEdit( edit ); } public void mouseReleased( MouseEvent e ) { super.mouseReleased( e ); Span span2; // resets the position to selection start if (and only if) the selection was // made anew, ctrl key is not pressed and transport is not running if( dragStarted && !shiftDrag && !ctrlDrag && !transport.isRunning() ) { span2 = timelineSel; // doc.timeline.getSelectionSpan(); if( !span2.isEmpty() && timelinePos != span2.getStart() ) { doc.timeline.editPosition( this, span2.getStart() ); } } dragStarted = false; validDrag = false; } public void mouseClicked( MouseEvent e ) { super.mouseClicked( e ); if( (e.getClickCount() == 2) && !e.isMetaDown() && !transport.isRunning() ) { transport.play( 1.0f ); } } // on Mac, Ctrl+Click is interpreted as // popup trigger by the system which means // no successive mouseDragged calls are made, // instead mouseMoved is called ... public void mouseMoved( MouseEvent e ) { super.mouseMoved( e ); mouseDragged( e ); } } @SuppressWarnings("serial") private static class SetCursorAction extends MenuAction { private final Component c; private final Cursor csr; public SetCursorAction(String name, KeyStroke stroke, Component c, Cursor csr) { super(name, stroke); this.c = c; this.csr = csr; } public void actionPerformed(ActionEvent e) { c.setCursor(csr); } } private class TimelineZoomTool extends TimelineTool { private boolean validDrag = false, dragStarted = false; private long startPos; private Point startPt; private long position; private final javax.swing.Timer zoomTimer; protected final Rectangle zoomRect = new Rectangle(); private MenuAction actionZoomIn = null; private MenuAction actionZoomOut = null; protected TimelineZoomTool() { zoomTimer = new javax.swing.Timer(250, new ActionListener() { public void actionPerformed(ActionEvent e) { setZoomRect(zoomRect); } }); } public void toolAcquired(final Component c) { super.toolAcquired(c); c.setCursor(zoomCsr[0]); if (c instanceof JComponent) { final JComponent jc = (JComponent) c; if (actionZoomOut == null) actionZoomOut = new SetCursorAction("zoomOut", KeyStroke.getKeyStroke(KeyEvent.VK_ALT, InputEvent.ALT_DOWN_MASK, false), c, zoomCsr[1]); if (actionZoomIn == null) actionZoomIn = new SetCursorAction("zoomIn", KeyStroke.getKeyStroke(KeyEvent.VK_ALT, 0, true), c, zoomCsr[0]); actionZoomOut.installOn(jc, JComponent.WHEN_IN_FOCUSED_WINDOW); actionZoomIn .installOn(jc, JComponent.WHEN_IN_FOCUSED_WINDOW); } } public void toolDismissed(Component c) { super.toolDismissed(c); if (c instanceof JComponent) { final JComponent jc = (JComponent) c; if (actionZoomOut != null) actionZoomOut.deinstallFrom(jc, JComponent.WHEN_IN_FOCUSED_WINDOW); if (actionZoomIn != null) actionZoomIn .deinstallFrom(jc, JComponent.WHEN_IN_FOCUSED_WINDOW); } } public void paintOnTop( Graphics2D g ) { // not necessary } public void mousePressed( MouseEvent e ) { super.mousePressed( e ); if( e.isAltDown() ) { dragStarted = false; validDrag = false; clickZoom( 2.0f, e ); } else { dragStarted = false; validDrag = true; processDrag( e, false ); } } public void mouseDragged( MouseEvent e ) { super.mouseDragged( e ); if( validDrag ) { if( !dragStarted ) { if( Math.abs( e.getX() - startPt.x ) > 2 ) { dragStarted = true; zoomTimer.restart(); } else return; } processDrag( e, true ); } } protected void cancelGesture() { zoomTimer.stop(); setZoomRect( null ); dragStarted = false; validDrag = false; } public void mouseReleased( MouseEvent e ) { super.mouseReleased( e ); Span span; if( dragStarted ) { cancelGesture(); span = new Span( Math.min( startPos, position ), Math.max( startPos, position )); if( !span.isEmpty() ) { doc.timeline.editScroll( this, span ); } } validDrag = false; } // zoom to mouse position public void mouseClicked(MouseEvent e) { super.mouseClicked(e); if (!e.isAltDown()) clickZoom(0.5f, e); } private void clickZoom(float factor, MouseEvent e) { long pos, visibleLen, start, stop; Span visibleSpan; visibleSpan = timelineVis; visibleLen = visibleSpan.getLength(); pos = visibleSpan.getStart() + (long) ((double) e.getX() / (double) getComponent().getWidth() * visibleSpan.getLength()); visibleLen = (long) (visibleLen * factor + 0.5f); if (visibleLen < 2) return; start = Math.max(0, Math.min(timelineLen, pos - (long) ((pos - visibleSpan.getStart()) * factor + 0.5f))); stop = start + visibleLen; if (stop > timelineLen) { stop = timelineLen; start = Math.max(0, stop - visibleLen); } visibleSpan = new Span(start, stop); if (!visibleSpan.isEmpty()) { doc.timeline.editScroll(this, visibleSpan); } } private void processDrag(MouseEvent e, boolean hasStarted) { final Point pt = SwingUtilities.convertPoint(e.getComponent(), e.getPoint(), wavePanel); Span span; int zoomX; span = timelineVis; position = span.getStart() + (long) (pt.getX() / getComponent().getWidth() * span.getLength()); position = Math.max(0, Math.min(timelineLen, position)); if (!hasStarted) { startPos = position; startPt = pt; } else { zoomX = Math.min(startPt.x, pt.x); zoomRect.setBounds(zoomX, waveView.getY() + 6, Math.abs(startPt.x - pt.x), waveView.getHeight() - 12); setZoomRect(zoomRect); } } } }