/*
* Created on Feb 8, 2007
*
* Copyright (c) 2007 Jens Gulden
*
* http://www.frinika.com
*
* This file is part of Frinika.
*
* Frinika is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
* Frinika is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with Frinika; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package com.frinika.sequencer.gui.menu;
import com.frinika.project.ProjectContainer;
import com.frinika.project.gui.ProjectFrame;
import com.frinika.gui.AbstractDialogAction;
import com.frinika.sequencer.gui.partview.PartView;
import com.frinika.sequencer.gui.selection.SelectionFocusable;
import com.frinika.sequencer.model.Part;
import com.frinika.sequencer.model.MidiPart;
import com.frinika.sequencer.model.MidiPartGhost;
import com.frinika.sequencer.model.Lane;
import com.frinika.sequencer.model.MultiEvent;
import com.frinika.sequencer.model.Selectable;
import javax.swing.JComponent;
import javax.swing.JMenuItem;
import java.awt.KeyboardFocusManager;
import java.awt.event.ActionEvent;
import java.util.Collection;
/**
* Menu-action for repeating the currently selected Part(s) or/and MIDI event(s).
* As a special feature, repetitions of MidiParts can be created as "Ghosts",
* which means that they appear as Parts in the Tracker-view, but do not contain
* their own events. Instead, Ghosts are internally linked to the origial Part from
* which they have been created. They represent the original Part transparently, but
* are not editable themselves. All changed applied to the original Part will
* immediately take effect on Ghosts also.
*
* (Although this is implemented as an extension of AbstractMidiAction, it is not
* actually a Midi-event related action only. But this is used for easier gui-dialog
* invocation.)
*
* @see com.frinika.sequencer.model.Ghost
* @see com.frinika.sequencer.model.MidiPartGhost
* @author Jens Gulden
*/
public class RepeatAction extends AbstractDialogAction {
int repeat = 1;
long repeatTicks;
boolean ghost = false;
long selectionLength;
boolean selectionSupportsGhosts;
protected Collection<Selectable> list;
public RepeatAction(ProjectFrame frame) {
super(frame, "sequencer.project.repeat");
repeatTicks = frame.getProjectContainer().getSequence().getResolution() * 4 * 4; // default: 4 bars
}
public void actionPerformed(ActionEvent e) {
if(!(java.awt.EventQueue.getCurrentEvent().getSource() instanceof JMenuItem))
if(!(KeyboardFocusManager.getCurrentKeyboardFocusManager().getPermanentFocusOwner() instanceof PartView)) return;
super.actionPerformed(e);
}
protected JComponent createGUI() {
return new RepeatActionEditor(this, frame.getProjectContainer());
}
protected void performPrepare() {
ProjectContainer project = frame.getProjectContainer();
SelectionFocusable focus = project.getSelectionFocus();
if (focus != null) {
list = focus.getObjects();
if ( ! list.isEmpty() ) {
// find first and last tick, and test if ghosts are possible
selectionSupportsGhosts = false;
long first = Long.MAX_VALUE;
long last = 0;
for (Selectable sel : list) {
long start = Long.MAX_VALUE;
long end = 0;
if (sel instanceof Part) {
start = ((Part)sel).getStartTick();
end = ((Part)sel).getEndTick();
if (sel instanceof MidiPart) {
selectionSupportsGhosts = true;
}
} else if (sel instanceof MultiEvent) {
start = ((MultiEvent)sel).getStartTick();
end = ((MultiEvent)sel).getEndTick();
}
if (start < first) {
first = start;
}
if (end > last) {
last = end;
}
}
if (first == Long.MAX_VALUE) {
first = 0;
}
selectionLength = last - first; // must be set before askOptions is called
} else {
cancel();
}
} else {
cancel();
}
}
protected void performAction() {
for (Selectable elem : list) {
if (elem instanceof Part) {
repeat((Part)elem);
} else if (elem instanceof MultiEvent) {
repeat((MultiEvent)elem);
}
}
}
private void repeat(Part part) {
long tick = repeatTicks;
Lane lane = part.getLane();
for (int i = 0; i < repeat; i++) {
if ((!ghost) || (!(part instanceof MidiPart)) || (part instanceof MidiPartGhost)) {
part.copyBy(tick, lane);
} else {
// create ghosts instead of real copies
assert (part instanceof MidiPart);
createGhost((MidiPart)part, tick);
}
tick += repeatTicks;
}
}
private void createGhost(MidiPart part, long deltaTicks) {
MidiPartGhost ghost = new MidiPartGhost(part, deltaTicks);
ghost.getLane().add(ghost);
}
private void repeat(MultiEvent event) {
long tick = repeatTicks;
Part part = event.getPart();
Lane lane = part.getLane();
for (int i = 0; i < repeat; i++) {
copyBy(event, tick); // (no ghosts for MultiEvents, only for MidiParts)
tick += repeatTicks;
}
}
private static void copyBy(MultiEvent event, long deltaTicks) {
try {
MultiEvent newEvent = (MultiEvent)event.clone();
long t = newEvent.getStartTick() + deltaTicks;
newEvent.setStartTick(t);
newEvent.getPart().add(newEvent);
} catch (CloneNotSupportedException cnse) {
cnse.printStackTrace();
}
}
}