package com.xenoage.zong.layout;
import static com.xenoage.utils.collections.CList.clist;
import static com.xenoage.utils.collections.CollectionUtils.alist;
import static com.xenoage.utils.kernel.Range.range;
import static com.xenoage.zong.layout.LayoutPos.layoutPos;
import java.util.ArrayList;
import java.util.List;
import com.xenoage.utils.annotations.Untested;
import com.xenoage.utils.collections.CList;
import com.xenoage.utils.math.geom.Point2f;
import com.xenoage.zong.core.Score;
import com.xenoage.zong.core.format.LayoutFormat;
import com.xenoage.zong.core.position.MP;
import com.xenoage.zong.layout.frames.FP;
import com.xenoage.zong.layout.frames.Frame;
import com.xenoage.zong.layout.frames.GroupFrame;
import com.xenoage.zong.layout.frames.ScoreFrame;
import com.xenoage.zong.layout.frames.ScoreFrameChain;
import com.xenoage.zong.musiclayout.ScoreLayoutPos;
import com.xenoage.zong.musiclayout.ScoreLayout;
import com.xenoage.zong.musiclayout.layouter.Context;
import com.xenoage.zong.musiclayout.layouter.ScoreLayoutArea;
import com.xenoage.zong.musiclayout.layouter.ScoreLayouter;
import com.xenoage.zong.musiclayout.layouter.Target;
import com.xenoage.zong.musiclayout.settings.LayoutSettings;
import com.xenoage.zong.symbols.SymbolPool;
import com.xenoage.zong.util.event.ScoreChangedEvent;
import lombok.AllArgsConstructor;
import lombok.Data;
/**
* Class for the layout of a score document.
*
* It consists of several pages, each containing several frames.
*
* @author Andreas Wenger
* @author Uli Teschemacher
*/
@Data @AllArgsConstructor public class Layout {
/** Default settings */
private LayoutDefaults defaults;
/** The list of pages */
private ArrayList<Page> pages;
/** The list of selected frames */
private ArrayList<Frame> selectedFrames;
/**
* Initializes an empty {@link Layout}.
*/
public Layout(LayoutDefaults defaults) {
this.defaults = defaults;
this.pages = alist();
this.selectedFrames = alist();
}
/**
* Adds a new page to this layout.
*/
public void addPage(Page page) {
page.setParentLayout(this);
pages.add(page);
}
/**
* Transforms the given {@link LayoutPos} to a {@link FP} of the frame at this position.
* If there is no frame, null is returned.
*/
public FP getFP(LayoutPos lp) {
if (lp == null)
return null;
Page page = pages.get(lp.pageIndex);
return page.getFP(lp.position);
}
/**
* Call this method when the given score has been changed.
*/
public void scoreChanged(ScoreChangedEvent event) {
updateScoreLayouts(event.getScore());
}
/**
* Computes the adjustment handle at the given position and returns it. If
* there is none, null is returned.
* @param layoutPosition the position where to look for a handle
* @param scaling the current scaling factor
*/
/* TODO
public FrameHandle computeFrameHandleAt(LayoutPosition layoutPosition, float scaling)
{
if (layoutPosition == null)
return null;
// find handle on the given page
FrameHandle handle = null;
Page givenPage = pages.get(layoutPosition.getPageIndex());
handle = givenPage.computeFrameHandleAt(layoutPosition.getPosition(), scaling);
return handle;
} */
/**
* Gets the axis-aligned bounding rectangle of the given system, specified
* by its {@link ScoreLayout}, the index of the frame and the index of the
* system (relative to the frame) in page coordinates together with the
* index of the page. If not found, null is returned.
*/
/* TODO
public Tuple2<Integer, Rectangle2f> getSystemBoundingRect(ScoreLayout scoreLayout,
int frameIndex, int systemIndex) {
//find the frame
ScoreFrameChain chain = scoreLayouts.getKeyByValue(scoreLayout);
ScoreFrame scoreFrame = chain.frames.get(frameIndex);
//get system boundaries in mm
ScoreFrameLayout scoreFrameLayout = scoreLayout.frames.get(frameIndex);
Rectangle2f rectMm = scoreFrameLayout.getSystemBoundaries(systemIndex);
if (rectMm == null)
return null;
//compute corner points (because frame may be rotated) and transform them
float x = rectMm.position.x - scoreFrame.getSize().width / 2;
float y = rectMm.position.y - scoreFrame.getSize().height / 2;
float w = rectMm.size.width;
float h = rectMm.size.height;
Point2f nw = scoreFrame.computePagePosition(new Point2f(x, y), this);
Point2f ne = scoreFrame.computePagePosition(new Point2f(x + w, y), this);
Point2f se = scoreFrame.computePagePosition(new Point2f(x + w, y + h), this);
Point2f sw = scoreFrame.computePagePosition(new Point2f(x, y + h), this);
// compute axis-aligned bounding box and return it
Rectangle2f ret = new Rectangle2f(nw.x, nw.y, 0, 0);
ret = ret.extend(ne);
ret = ret.extend(se);
ret = ret.extend(sw);
int pageIndex = pages.indexOf(getPage(scoreFrame));
return new Tuple2<Integer, Rectangle2f>(pageIndex, ret);
} */
/**
* Gets the {@link ScoreFrame} which contains the given measure within
* the given score. If it can not be found, null is returned.
*/
@Untested public ScoreFrame getScoreFrame(Score score, int measure) {
ScoreFrameChain chain = getScoreFrameChain(score);
if (chain == null || chain.getScoreLayout() == null)
return null;
int frameIndex = chain.getScoreLayout().getFrameIndexOf(measure);
if (frameIndex >= 0 && frameIndex < chain.getFrames().size())
return chain.getFrames().get(frameIndex);
return null;
}
/**
* Updates the {@link ScoreLayout}s belonging to the given {@link Score}.
*/
public void updateScoreLayouts(Score score) {
ScoreFrameChain chain = getScoreFrameChain(score);
if (chain == null)
return;
ScoreLayout oldScoreLayout = chain.getScoreLayout();
//select symbol pool and layout settings
SymbolPool symbolPool = oldScoreLayout != null ?
oldScoreLayout.symbolPool : defaults.getSymbolPool();
LayoutSettings layoutSettings = oldScoreLayout != null ?
oldScoreLayout.layoutSettings : defaults.getLayoutSettings();
CList<ScoreLayoutArea> areas = clist();
for (ScoreFrame scoreFrame : chain.getFrames()) {
areas.add(new ScoreLayoutArea(scoreFrame.getSize(), scoreFrame.getHFill(), scoreFrame.getVFill()));
}
areas.close();
Context context = new Context(score, symbolPool, layoutSettings);
Target target = new Target(areas, areas.get(areas.size() - 1), false);
ScoreLayout scoreLayout = new ScoreLayouter(context, target).createScoreLayout();
//set updated layout
chain.setScoreLayout(scoreLayout);
}
/**
* Sets all pages to the given format.
* The defaults values are not changed.
*/
public void setLayoutFormat(LayoutFormat layoutFormat) {
for (int i : range(pages)) {
pages.get(i).setFormat(layoutFormat.getPageFormat(i));
}
}
public void chainUpScoreFrame(ScoreFrame frameFrom, ScoreFrame frameTo) {
if (frameFrom == frameTo)
throw new IllegalArgumentException("Same frames");
ScoreFrameChain fromChain = frameFrom.getScoreFrameChain();
fromChain.add(frameFrom, frameTo);
}
/**
* Returns the {@link LayoutPos} of the given {@link MP} within the given {@link Score}
* at the given line position, or null if unknown.
*/
public LayoutPos computeLP(Score score, MP mp, float lp) {
ScoreFrameChain chain = getScoreFrameChain(score);
if (chain != null) {
ScoreLayout sl = chain.getScoreLayout();
ScoreLayoutPos slp = sl.getScoreLP(mp, lp);
if (slp != null) {
ScoreFrame frame = chain.getFrames().get(slp.frameIndex);
Page page = frame.getParentPage();
if (page != null) {
Point2f frameP = slp.pMm.sub(frame.getSize().width / 2, frame.getSize().height / 2);
int pageIndex = pages.indexOf(page);
Point2f pMm = frame.getPagePosition(frameP);
return layoutPos(this, pageIndex, pMm);
}
}
}
return null;
}
/**
* Gets the {@link ScoreFrameChain} for the given {@link Score}, or null
* if it can not be found.
*/
public ScoreFrameChain getScoreFrameChain(Score score) {
for (ScoreFrame frame : getScoreFrames()) {
ScoreFrameChain chain = frame.getScoreFrameChain();
if (chain != null && chain.getScore() == score)
return chain;
}
return null;
}
/**
* Gets a list with all {@link ScoreFrame}s in this layout.
*/
public List<ScoreFrame> getScoreFrames() {
List<ScoreFrame> ret = alist();
for (Page page : pages) {
for (Frame frame : page.getFrames()) {
if (frame instanceof ScoreFrame)
ret.add((ScoreFrame) frame);
else if (frame instanceof GroupFrame)
ret.addAll(((GroupFrame) frame).getScoreFrames());
}
}
return ret;
}
}