diff --git a/history.md b/history.md
index 248817e..2ea74c1 100644
--- a/history.md
+++ b/history.md
@@ -1,6 +1,13 @@
### Version History
+#### v0.1.27
+- Added package `msmEditingTools` with class `MsmEditingTools` to create context menu popups when right-clicking the MSM tree.
+- Right now there is only one such context menu for the MSM root node that offers to apply every `sequencingMap` to the affected MSM and MPM maps.
+ - The `sequencingMaps` are then deleted from the MSM as they are no longer valid.
+ - It is recommended to operate this functionality BEFORE making any entries in the graphical score as they will get lost in the process.
+
+
#### v0.1.26
- Dependency updates
- WebLaF Core v1.2.13 to v1.2.14
diff --git a/src/mpmToolbox/Main.java b/src/mpmToolbox/Main.java
index ae4b608..8bc8320 100644
--- a/src/mpmToolbox/Main.java
+++ b/src/mpmToolbox/Main.java
@@ -13,7 +13,7 @@
* @author Axel Berndt
*/
public class Main {
- public static final String version = "0.1.26";
+ public static final String version = "0.1.27";
public static void main(String[] args) {
// read the application settings from file
diff --git a/src/mpmToolbox/gui/mpmEditingTools/MpmEditingTools.java b/src/mpmToolbox/gui/mpmEditingTools/MpmEditingTools.java
index a8a8c69..d560810 100644
--- a/src/mpmToolbox/gui/mpmEditingTools/MpmEditingTools.java
+++ b/src/mpmToolbox/gui/mpmEditingTools/MpmEditingTools.java
@@ -24,8 +24,6 @@
import mpmToolbox.gui.mpmTree.MpmStyleCollection;
import mpmToolbox.gui.mpmTree.MpmTree;
import mpmToolbox.gui.mpmTree.MpmTreeNode;
-import mpmToolbox.gui.msmTree.MsmTree;
-import mpmToolbox.gui.msmTree.MsmTreeNode;
import mpmToolbox.projectData.score.Score;
import mpmToolbox.projectData.score.ScoreNode;
import mpmToolbox.projectData.score.ScorePage;
@@ -878,22 +876,6 @@ public static WebPopupMenu makeScoreContextMenu(@NotNull MpmTreeNode mpmTreeNode
return menu;
}
- /**
- * This creates the context menu in the ScoreDisplayPanel when an MSM object is right-clicked.
- * @return
- */
- public static WebPopupMenu makeScoreContextMenu(@NotNull MsmTreeNode msmTreeNode, @NotNull MsmTree msmTree, @NotNull ScorePage scorePage) {
- WebMenuItem deleteFromScore = new WebMenuItem("Remove from Score");
- deleteFromScore.addActionListener(actionEvent -> {
- scorePage.removeEntry((Element) msmTreeNode.getUserObject()); // remove the note from the score page graph structure
- msmTree.updateNode(msmTreeNode); // update the MsmTree
- });
-
- WebPopupMenu menu = new WebPopupMenu();
- menu.add(deleteFromScore);
- return menu;
- }
-
/**
* changes in the performance should also be visible in the alignment that is currently shown in the audio frame
* @param performance the performance where the changes were made
diff --git a/src/mpmToolbox/gui/mpmTree/MpmTree.java b/src/mpmToolbox/gui/mpmTree/MpmTree.java
index b147a93..320dc4f 100644
--- a/src/mpmToolbox/gui/mpmTree/MpmTree.java
+++ b/src/mpmToolbox/gui/mpmTree/MpmTree.java
@@ -213,7 +213,6 @@ public void mouseClicked(MouseEvent mouseEvent) {
*/
@Override
public void mousePressed(MouseEvent mouseEvent) {
-
}
/**
@@ -222,7 +221,6 @@ public void mousePressed(MouseEvent mouseEvent) {
*/
@Override
public void mouseReleased(MouseEvent mouseEvent) {
-
}
/**
@@ -231,7 +229,6 @@ public void mouseReleased(MouseEvent mouseEvent) {
*/
@Override
public void mouseEntered(MouseEvent mouseEvent) {
-
}
/**
@@ -240,7 +237,6 @@ public void mouseEntered(MouseEvent mouseEvent) {
*/
@Override
public void mouseExited(MouseEvent mouseEvent) {
-
}
@Override
diff --git a/src/mpmToolbox/gui/msmEditingTools/MsmEditingTools.java b/src/mpmToolbox/gui/msmEditingTools/MsmEditingTools.java
new file mode 100644
index 0000000..8ee35c6
--- /dev/null
+++ b/src/mpmToolbox/gui/msmEditingTools/MsmEditingTools.java
@@ -0,0 +1,167 @@
+package mpmToolbox.gui.msmEditingTools;
+
+import com.alee.api.annotations.NotNull;
+import com.alee.laf.menu.WebMenuItem;
+import com.alee.laf.menu.WebPopupMenu;
+import meico.mei.Helper;
+import meico.mpm.elements.Part;
+import meico.mpm.elements.Performance;
+import meico.mpm.elements.maps.ArticulationMap;
+import meico.mpm.elements.maps.GenericMap;
+import meico.msm.Msm;
+import mpmToolbox.gui.mpmTree.MpmTree;
+import mpmToolbox.gui.msmTree.MsmTree;
+import mpmToolbox.gui.msmTree.MsmTreeNode;
+import mpmToolbox.projectData.score.ScorePage;
+import nu.xom.Element;
+import nu.xom.Elements;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/**
+ * Helper class for MsmTreeNode. It generates the context menu that appears when right clicking
+ * in the MSM tree and all functionality required for it.
+ * @author Axel Berndt
+ */public class MsmEditingTools {
+ public static WebPopupMenu makeMsmTreeContextMenu(@NotNull MsmTreeNode forThisNode, @NotNull MsmTree inThisMsmTree) {
+ MsmTreeNode self = forThisNode;
+ MsmTree msmTree = inThisMsmTree;
+
+// System.out.println("Creating popup menu for " + self.name);
+ WebPopupMenu menu = new WebPopupMenu();
+
+ switch (self.getType()) {
+ case msm:
+ // resolve all sequencingMaps
+ WebMenuItem expandRepetitions = new WebMenuItem("Resolve all sequencingMaps");
+ expandRepetitions.setToolTipText("Creates \"through-composed\" MSM and performances.
Deletes all sequencingMaps afterwards. If you plan to use
this functionality, do it before making entries in the score!");
+ expandRepetitions.addActionListener(actionEvent -> MsmEditingTools.resolveAllSequencingMaps(msmTree));
+ menu.add(expandRepetitions);
+ break;
+
+ case attribute:
+ break;
+
+ case global:
+ break;
+
+ case part:
+ break;
+
+ case header:
+ break;
+
+ case dated:
+ break;
+
+ case score:
+ break;
+
+ case sequencingMap:
+ break;
+
+ case note:
+ break;
+
+ case rest:
+ break;
+
+ case lyrics:
+ break;
+
+ case element:
+ default:
+ break;
+ }
+
+ return menu;
+
+ }
+
+ /**
+ * Invoke this method to immediately open the editor dialog of MSM nodes ... at least those that have
+ * an editor dialog and are leaf nodes in the MSM tree. Non-leaf nodes expand on double click and that
+ * is what this method is meant to be used for, open the editor on double click.
+ * @param forThisNode
+ * @param inThisMsmTree
+ */
+ public static void quickOpenEditor(@NotNull MsmTreeNode forThisNode, @NotNull MsmTree inThisMsmTree) {
+ MsmTreeNode self = forThisNode;
+ MsmTree msmTree = inThisMsmTree;
+
+ switch (self.getType()) {
+ // TODO: any editing of the MSM data?
+ default:
+ break;
+ }
+ }
+
+ /**
+ * This creates the context menu in the ScoreDisplayPanel when an MSM object is right-clicked.
+ * @return
+ */
+ public static WebPopupMenu makeScoreContextMenu(@NotNull MsmTreeNode msmTreeNode, @NotNull MsmTree msmTree, @NotNull ScorePage scorePage) {
+ WebMenuItem deleteFromScore = new WebMenuItem("Remove from Score");
+ deleteFromScore.addActionListener(actionEvent -> {
+ scorePage.removeEntry((Element) msmTreeNode.getUserObject()); // remove the note from the score page graph structure
+ msmTree.updateNode(msmTreeNode); // update the MsmTree
+ });
+
+ WebPopupMenu menu = new WebPopupMenu();
+ menu.add(deleteFromScore);
+ return menu;
+ }
+
+ /**
+ * apply all sequencingMaps to the MSM tree and the MPM performances, remove the sequencingMaps from the MSM
+ * @param msmTree
+ */
+ private static void resolveAllSequencingMaps(@NotNull MsmTree msmTree) {
+ Msm msm = msmTree.getProjectPane().getMsm();
+ MpmTree mpmTree = msmTree.getProjectPane().getMpmTree();
+
+ ArrayList articulationMaps = new ArrayList<>();
+
+ // apply the sequencingMaps to MPM data first
+ Element globalSequencingMap = msm.getRootElement().getFirstChildElement("global").getFirstChildElement("dated").getFirstChildElement("sequencingMap");
+ for (Performance performance : mpmTree.getProjectPane().getMpm().getAllPerformances()) {
+ if (globalSequencingMap != null) {
+ HashMap maps = performance.getGlobal().getDated().getAllMaps();
+ for (GenericMap map : maps.values()) {
+ map.applySequencingMap(globalSequencingMap);
+ if (map instanceof ArticulationMap) // in articulationMaps the elements have notid attribute that has to be updated after resolving the sequencingmaps in MSM
+ articulationMaps.add(map); // so keep the articulationMaps for later reference
+ }
+ }
+ Elements msmParts = msm.getParts();
+ ArrayList mpmParts = performance.getAllParts();
+ for (int pa=0; pa < performance.size(); ++pa) {
+ Element msmPart = msmParts.get(pa);
+ Element sequencingMap = msmPart.getFirstChildElement("dated").getFirstChildElement("sequencingMap");
+ if (sequencingMap == null) {
+ sequencingMap = globalSequencingMap;
+ if (sequencingMap == null)
+ continue;
+ }
+ for (GenericMap map : mpmParts.get(pa).getDated().getAllMaps().values()) {
+ map.applySequencingMap(sequencingMap);
+ if (map instanceof ArticulationMap) // in articulationMaps the elements have notid attribute that has to be updated after resolving the sequencingmaps in MSM
+ articulationMaps.add(map); // so keep the articulationMaps for later reference
+ }
+ }
+ }
+
+ // apply the sequencingMaps to MSM data, this will also delete the sequencingMaps
+ HashMap repetitionIDs = msm.resolveSequencingMaps();
+
+ // update the articulationMap's elements' noteid attributes
+ for (GenericMap map : articulationMaps)
+ Helper.updateMpmNoteidsAfterResolvingRepetitions(map, repetitionIDs);
+
+ // reload the nodes whose content changed
+ msmTree.reloadNode(msmTree.getRootNode());
+ mpmTree.reloadNode(mpmTree.getRootNode());
+ msmTree.getProjectPane().getScore().cleanupDeadNodes();
+ }
+}
diff --git a/src/mpmToolbox/gui/msmTree/MsmTree.java b/src/mpmToolbox/gui/msmTree/MsmTree.java
index 53ec883..8f84add 100644
--- a/src/mpmToolbox/gui/msmTree/MsmTree.java
+++ b/src/mpmToolbox/gui/msmTree/MsmTree.java
@@ -13,10 +13,15 @@
import nu.xom.Element;
import nu.xom.Node;
+import javax.swing.*;
+import javax.swing.event.TreeModelEvent;
+import javax.swing.event.TreeModelListener;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
import java.util.ArrayList;
import java.util.Enumeration;
@@ -24,7 +29,7 @@
* A custom WebAsncTree for MSM data.
* @author Axel Berndt
*/
-public class MsmTree extends WebExTree implements TreeSelectionListener/*, MouseListener*/ {
+public class MsmTree extends WebExTree implements MouseListener, TreeSelectionListener, TreeModelListener {
@NotNull private final ProjectPane projectPane; // a link to the parent project pane to access its data, midi player etc.
private WebDockableFrame dockableFrame = null; // a WebDockableFrame instance that displays this MSM tree, to be used in class ProjectPane
@@ -44,8 +49,9 @@ public MsmTree(@NotNull ProjectPane projectPane) {
// msmTree.setCellEditor(new MsmTreeCellEditor());
// msmTree.setStyleId(StyleId.treeTransparent);
+ this.addMouseListener(this);
this.addTreeSelectionListener(this);
-// this.addMouseListener(this);
+ this.treeModel.addTreeModelListener(this);
}
// /**
@@ -206,4 +212,77 @@ public WebDockableFrame getDockableFrame() {
return this.dockableFrame;
}
+
+ /**
+ * When the user clicked in the tree, perform this action.
+ * @param mouseEvent
+ */
+ @Override
+ public void mouseClicked(MouseEvent mouseEvent) {
+ // right click opens the context menu of the clicked node
+ if (SwingUtilities.isRightMouseButton(mouseEvent)) { // if right click
+ MsmTreeNode node = this.getNodeForRow(this.getClosestRowForLocation(mouseEvent.getX(), mouseEvent.getY())); // get the node that has been clicked
+ node.getContextMenu(this).show(this, mouseEvent.getX() - 25, mouseEvent.getY()); // trigger its context menu
+ return;
+ }
+ if (SwingUtilities.isLeftMouseButton(mouseEvent)) { // if left click
+ if (mouseEvent.getClickCount() > 1) { // if double (or more) click -> open editor dialog
+ MsmTreeNode node = this.getSelectedNode(); // get the node that has been double-clicked
+ node.openEditorDialog(this);
+ }
+ }
+ }
+
+ /**
+ * Perform this action when mouse button is pressed.
+ * @param mouseEvent
+ */
+ @Override
+ public void mousePressed(MouseEvent mouseEvent) {
+ }
+
+ /**
+ * Perform this action when mouse button is released.
+ * @param mouseEvent
+ */
+ @Override
+ public void mouseReleased(MouseEvent mouseEvent) {
+ }
+
+ /**
+ * Perform this action when the mouse cursor enters the MpmTree widget.
+ * @param mouseEvent
+ */
+ @Override
+ public void mouseEntered(MouseEvent mouseEvent) {
+ }
+
+ /**
+ * perform this action when the mouse cursor exits the MpmTree widget.
+ * @param mouseEvent
+ */
+ @Override
+ public void mouseExited(MouseEvent mouseEvent) {
+ }
+
+ @Override
+ public void treeNodesChanged(TreeModelEvent treeModelEvent) {
+ }
+
+ @Override
+ public void treeNodesInserted(TreeModelEvent treeModelEvent) {
+ }
+
+ @Override
+ public void treeNodesRemoved(TreeModelEvent treeModelEvent) {
+ }
+
+ /**
+ * if anything in the tree structure changed the score display gets updated
+ * @param treeModelEvent
+ */
+ @Override
+ public void treeStructureChanged(TreeModelEvent treeModelEvent) {
+ this.projectPane.repaintScoreDisplay(); // repaint the score display so a selected MpmTreeNode gets highlighted and when switching to another performance we get to see its overlay
+ }
}
diff --git a/src/mpmToolbox/gui/msmTree/MsmTreeNode.java b/src/mpmToolbox/gui/msmTree/MsmTreeNode.java
index bb66c76..1620ccb 100644
--- a/src/mpmToolbox/gui/msmTree/MsmTreeNode.java
+++ b/src/mpmToolbox/gui/msmTree/MsmTreeNode.java
@@ -3,11 +3,13 @@
import com.alee.api.annotations.NotNull;
import com.alee.api.ui.TextBridge;
import com.alee.extended.tree.WebExTree;
+import com.alee.laf.menu.WebPopupMenu;
import com.alee.laf.tree.TreeNodeParameters;
import com.alee.laf.tree.UniqueNode;
import meico.mei.Helper;
import meico.midi.EventMaker;
import meico.midi.MidiPlayer;
+import mpmToolbox.gui.msmEditingTools.MsmEditingTools;
import mpmToolbox.projectData.ProjectData;
import nu.xom.Attribute;
import nu.xom.Element;
@@ -18,8 +20,6 @@
import javax.sound.midi.Track;
import javax.swing.*;
-import static mpmToolbox.gui.msmTree.MsmTreeNode.XmlNodeType.root;
-
/**
* Instances of this class represent MSM data (Element, Attribute or Node) in a WebAsyncTree
* @author Axel Berndt
@@ -64,7 +64,7 @@ public MsmTreeNode(@NotNull final Element xml, @NotNull ProjectData project) {
// determine type
switch (xml.getLocalName()) {
case "msm":
- this.type = root;
+ this.type = XmlNodeType.msm;
break;
case "global":
this.type = XmlNodeType.global;
@@ -72,12 +72,18 @@ public MsmTreeNode(@NotNull final Element xml, @NotNull ProjectData project) {
case "part":
this.type = XmlNodeType.part;
break;
+ case "header":
+ this.type = XmlNodeType.header;
+ break;
case "dated":
- this.type = XmlNodeType.score;
+ this.type = XmlNodeType.dated;
break;
case "score":
this.type = XmlNodeType.score;
break;
+ case "sequencingMap":
+ this.type = XmlNodeType.sequencingMap;
+ break;
case "note":
this.type = XmlNodeType.note;
break;
@@ -119,8 +125,8 @@ public MsmTreeNode(@NotNull final Attribute attribute, @NotNull ProjectData proj
*/
private void generateMyName() {
switch (this.type) {
- case root:
- this.name = "</> " + ((Element)this.getUserObject()).getLocalName() + "";
+ case msm:
+ this.name = "</> msm";
break;
case part:
this.name = ((Element)this.getUserObject()).getLocalName().concat(" " + ((Element)this.getUserObject()).getAttributeValue("number")).concat(" " + ((Element)this.getUserObject()).getAttributeValue("name"));
@@ -128,6 +134,9 @@ private void generateMyName() {
case score:
this.name = "♫ " + ((Element)this.getUserObject()).getLocalName() + "";
break;
+ case sequencingMap:
+ this.name = "sequencingMap";
+ break;
case note: {
int ppq = this.project.getMsm().getPPQ();
double duration = Double.parseDouble(((Element)this.getUserObject()).getAttributeValue("duration"));
@@ -167,10 +176,15 @@ private void generateMyName() {
case lyrics:
this.name = ((Element)this.getUserObject()).getLocalName() + " \"" + ((Element)this.getUserObject()).getValue() + "\"";
break;
+ case element:
+ break;
case attribute:
this.name = "@ " + ((Attribute)this.getUserObject()).getLocalName() + " " + this.getUserObject().getValue() + "";
// this.name = "@ " + ((Attribute)this.getUserObject()).getLocalName() + ": " + this.getUserObject().getValue();
break;
+ case global:
+ case dated:
+ case header:
default:
this.name = ((Element)this.getUserObject()).getLocalName();
}
@@ -184,6 +198,23 @@ protected void update() {
this.generateMyName();
}
+ /**
+ * This method creates the context menu when the node is right-clicked.
+ * @param msmTree the MsmTree instance that this node belongs to
+ */
+ public WebPopupMenu getContextMenu(@NotNull MsmTree msmTree) {
+ return MsmEditingTools.makeMsmTreeContextMenu(this, msmTree);
+ }
+
+ /**
+ * This method will trigger the editor dialog for the node, provided it has one.
+ * It is meant to be invoked by a double click event in class MsmTree.
+ * @param msmTree
+ */
+ public void openEditorDialog(@NotNull MsmTree msmTree) {
+ MsmEditingTools.quickOpenEditor(this, msmTree);
+ }
+
/**
* Access the XML object behind this node.
* @return
@@ -296,7 +327,7 @@ public void play(MidiPlayer midiPlayer) {
*/
public Element getPart() {
switch (this.getType()) {
- case root:
+ case msm:
case global:
return null;
default: {
@@ -312,15 +343,17 @@ public Element getPart() {
* an enumeration of the node types
*/
public enum XmlNodeType {
- root, // a root node
- element, // an element
- attribute, // an attribute
- global, // a global element
- part, // a part element
- dated, // a dated element
- score, // a score element
- note, // a note element
- rest, // a rest element
- lyrics // a lyrics element
+ msm, // an msm node
+ element, // an element
+ attribute, // an attribute
+ global, // a global element
+ part, // a part element
+ header, // a header element
+ dated, // a dated element
+ score, // a score element
+ sequencingMap, // a sequencingMap element
+ note, // a note element
+ rest, // a rest element
+ lyrics // a lyrics element
}
}
diff --git a/src/mpmToolbox/gui/score/ScoreDisplayPanel.java b/src/mpmToolbox/gui/score/ScoreDisplayPanel.java
index d9ec6d0..ef7f586 100644
--- a/src/mpmToolbox/gui/score/ScoreDisplayPanel.java
+++ b/src/mpmToolbox/gui/score/ScoreDisplayPanel.java
@@ -12,6 +12,7 @@
import mpmToolbox.gui.mpmEditingTools.PlaceAndCreateContextMenu;
import mpmToolbox.gui.mpmTree.MpmTree;
import mpmToolbox.gui.mpmTree.MpmTreeNode;
+import mpmToolbox.gui.msmEditingTools.MsmEditingTools;
import mpmToolbox.gui.msmTree.MsmTree;
import mpmToolbox.gui.msmTree.MsmTreeNode;
import mpmToolbox.projectData.score.Score;
@@ -571,7 +572,7 @@ private void dragOrSelectGesture(MouseEvent mouseEvent) {
case MouseEvent.BUTTON1: // left click
break;
case MouseEvent.BUTTON3: // right click = context menu
- WebPopupMenu menu = MpmEditingTools.makeScoreContextMenu(msmTreeNode, msmTree, scorePage);
+ WebPopupMenu menu = MsmEditingTools.makeScoreContextMenu(msmTreeNode, msmTree, scorePage);
menu.show(this, mouseEvent.getX() - 25, mouseEvent.getY());
break;
}
@@ -814,7 +815,7 @@ public void mouseReleased(MouseEvent mouseEvent) {
if (msmTreeNode != null) { // if something has been selected
msmTree.setSelectedNode(msmTreeNode); // select the node in the msm tree
msmTree.scrollPathToVisible(msmTreeNode.getTreePath()); // scroll the tree so the node is visible
- WebPopupMenu menu = MpmEditingTools.makeScoreContextMenu(msmTreeNode, msmTree, scorePage);
+ WebPopupMenu menu = MsmEditingTools.makeScoreContextMenu(msmTreeNode, msmTree, scorePage);
menu.show(this, mouseEvent.getX() - 25, mouseEvent.getY());
}
break;
diff --git a/src/mpmToolbox/projectData/score/Score.java b/src/mpmToolbox/projectData/score/Score.java
index 6aa3064..b7c7ecc 100644
--- a/src/mpmToolbox/projectData/score/Score.java
+++ b/src/mpmToolbox/projectData/score/Score.java
@@ -273,10 +273,11 @@ public int cleanupDeadNodes() {
for (ScorePage page : this.pages) {
ArrayList toBeRemoved = new ArrayList<>();
for (Element element : page.getAllEntries().keySet()) { // for each element that is linked in this score page
- if (element.getLocalName().equals("note")) // notes cannot be removed since we are not allowed to edit the MSM
- continue;
+ if (element.getLocalName().equals("note") // if we have a note element
+ && (element.getDocument() == this.parentProject.getMsm().getDocument())) // if it is still linked in the MSM document
+ continue; // we keep it
if ((this.parentProject.getMpm() != null) // if we have an MPM document
- && (element.getDocument() == this.parentProject.getMpm().getDocument())) // if the element is still linked in the MPM document
+ && (element.getDocument() == this.parentProject.getMpm().getDocument())) // if the element is still linked in the MPM document
continue; // we keep it
toBeRemoved.add(element); // otherwise it is dd and should be removed from the score
}