/* Copyright (C) 2006 Christian Schneider
*
* This file is part of Nomad.
*
* Nomad 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.
*
* Nomad 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 Nomad; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.sf.nmedit.jtheme.clavia.nordmodular;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics2D;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.event.ActionEvent;
import java.awt.event.ContainerEvent;
import java.awt.event.ContainerListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ImageIcon;
import javax.swing.JMenu;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JViewport;
import javax.swing.UIDefaults;
import net.sf.nmedit.jpatch.PConnection;
import net.sf.nmedit.jpatch.PConnector;
import net.sf.nmedit.jpatch.PConnectorDescriptor;
import net.sf.nmedit.jpatch.PModule;
import net.sf.nmedit.jpatch.PSignal;
import net.sf.nmedit.jpatch.PSignalTypes;
import net.sf.nmedit.jpatch.clavia.nordmodular.Format;
import net.sf.nmedit.jpatch.clavia.nordmodular.NMPatch;
import net.sf.nmedit.jpatch.clavia.nordmodular.VoiceArea;
import net.sf.nmedit.jpatch.clavia.nordmodular.parser.ParseException;
import net.sf.nmedit.jpatch.event.PConnectionEvent;
import net.sf.nmedit.jpatch.event.PConnectionListener;
import net.sf.nmedit.jpatch.history.PUndoableEditSupport;
import net.sf.nmedit.jsynth.clavia.nordmodular.utils.NmUtils;
import net.sf.nmedit.jtheme.JTContext;
import net.sf.nmedit.jtheme.cable.Cable;
import net.sf.nmedit.jtheme.cable.JTCableManager;
import net.sf.nmedit.jtheme.clavia.nordmodular.misc.NMPatchImageExporter;
import net.sf.nmedit.jtheme.component.JTConnector;
import net.sf.nmedit.jtheme.component.JTModule;
import net.sf.nmedit.jtheme.component.JTModuleContainer;
import net.sf.nmedit.jtheme.component.JTPatch;
import net.sf.nmedit.jtheme.component.plaf.mcui.ContainerAction;
import net.sf.nmedit.jtheme.store2.ModuleElement;
import net.sf.nmedit.jtheme.util.JThemeUtils;
import net.sf.nmedit.nmutils.Platform;
import net.sf.nmedit.nmutils.graphics.GraphicsToolkit;
public class JTNMPatch extends JTPatch implements Transferable, PropertyChangeListener
{
/**
*
*/
private static final long serialVersionUID = -2196920112843451844L;
private JTPatchSettingsBar settings;
private NMPatch patch;
private ModuleContainerEventHandler ehPoly;
private ModuleContainerEventHandler ehCommon;
private JScrollPane spPoly;
private JScrollPane spCommon;
private NMSplitPane split;
public JTNMPatch(JTNM1Context context, NMPatch patch) throws Exception
{
super(context);
this.patch = patch;
split = new NMSplitPane(JSplitPane.VERTICAL_SPLIT);
split.setOneTouchExpandable(true);
split.setContinuousLayout(false);
split.setTopComponent(spPoly = createVoiceArea(patch.getPolyVoiceArea()));
split.setBottomComponent(spCommon = createVoiceArea(patch.getCommonVoiceArea()));
split.setResizeWeight(1);
split.setDividerLocation(patch.getHeader().getSeparatorPosition());
split.setStoredDividerLocation(patch.getHeader().getSeparatorPosition());
split.setDividerSize(12);
split.addPropertyChangeListener(NMSplitPane.STORED_SPLIT_PROPERTY, this);
settings = new JTPatchSettingsBar(this);
setLayout(new BorderLayout());
add(split, BorderLayout.CENTER);
add(settings, BorderLayout.NORTH);
}
public NMPatchImageExporter createPatchImageExporter()
{
return new NMPatchImageExporter(this);
}
protected static class ModuleAction extends AbstractAction
{
/**
*
*/
private static final long serialVersionUID = -3424440102953404258L;
public static final String RENAME = "rename";
public static final String HELP = "help";
public static final String COLOR = "color";
private JTModule module;
private static ImageIcon[] moduleColors;
private static ImageIcon[] getModuleColors(JTModule module)
{
if (moduleColors != null)
return moduleColors;
List<ImageIcon> list = new ArrayList<ImageIcon>();
UIDefaults def = module.getContext().getUIDefaults();
Color defaultColor = def.getColor("module.background");
if (defaultColor != null)
list.add(GraphicsToolkit.renderColorIcon(defaultColor));
int i=1;
while (true)
{
Color c = def.getColor("module.background$"+i);
if (c == null) break;
list.add(GraphicsToolkit.renderColorIcon(c));
i++;
}
moduleColors = list.<ImageIcon>toArray(new ImageIcon[list.size()]);
return moduleColors;
}
public static JPopupMenu createPopup(JTModule module)
{
JPopupMenu popup = new JPopupMenu();
popup.add(new ModuleAction(RENAME, module));
popup.addSeparator();
ImageIcon[] colors = getModuleColors(module);
if (colors.length>0)
{
JMenu colorMenu = new JMenu("Color");
popup.add(colorMenu);
for (int i=0;i<colors.length;i++)
{
ModuleAction a = new ModuleAction(COLOR, module);
a.putValue(ModuleAction.SMALL_ICON, colors[i]);
a.putValue("bg.colorindex", i);
if (i == 0)
a.putValue(ModuleAction.NAME, "Default Color");
else
a.putValue(ModuleAction.NAME, "Color "+(i+1));
colorMenu.add(a);
}
popup.addSeparator();
}
popup.add(new ModuleAction(HELP, module));
JTModuleContainer container = (JTModuleContainer) module.getParent();
Action deleteAction = container.getActionMap().get(ContainerAction.DELETE);
if (deleteAction != null)
{
popup.addSeparator();
popup.add(deleteAction);
}
return popup;
}
public ModuleAction(String action, JTModule module)
{
this.module = module;
putValue(ACTION_COMMAND_KEY, action);
if (action == RENAME)
{
putValue(NAME, "Rename");
setEnabled(false);
}
else if (action == HELP)
{
putValue(NAME, "Help");
}
else if (action == COLOR)
{
putValue(NAME, "color");
}
else setEnabled(false);
}
public void actionPerformed(ActionEvent e)
{
if (!isEnabled())
return;
if (module == null)
return;
String command = e.getActionCommand();
if (command == RENAME)
{
throw new UnsupportedOperationException("command not supported: "+command);
}
else if (command == HELP)
{
module.showHelpFor(module.getModule());
}
else
{
setColor();
}
}
private void setColor()
{
Integer colorIndex = (Integer) getValue("bg.colorindex");
if (colorIndex != null)
setColor(colorIndex.intValue());
}
private void setColor(int colorIndex)
{
for (JTModule m : ((JTModuleContainer) module.getParent()).getModules())
{
if (m.isSelected() || m == module)
{
String title = m.getTitle();
if (title == null) title = "";
title = JThemeUtils.setColorKey(title, colorIndex);
m.setTitle(title);
}
}
}
}
protected static class ConnectorAction extends AbstractAction
{
/**
*
*/
private static final long serialVersionUID = -6831064642989411996L;
public static final String DISCONNECT = "Disconnect";
public static final String BREAK = "Break";
public static final String CAUDIO = "Audio";
public static final String CCTRL = "Control";
public static final String CLOGIC = "Logic";
public static final String CSLAVE = "Slave";
public static final String CU1 = "User1";
public static final String CU2 = "User2";
public static final String DELETE = "Delete";
private JTConnector connector;
private PSignal signalType;
public static JPopupMenu createPopup(JTConnector connector)
{
JPopupMenu popup = new JPopupMenu();
popup.add(new ConnectorAction(DISCONNECT, connector, null));
popup.add(new ConnectorAction(BREAK, connector, null));
popup.addSeparator();
PSignalTypes signalTypes = getSignalTypes(connector);
JMenu colorItem = new JMenu("Color");
popup.add(colorItem);
for (PSignal s: signalTypes)
{
colorItem.add(new ConnectorAction(s.getName(), connector, s));
}
popup.addSeparator();
popup.add(new ConnectorAction(DELETE, connector, null));
return popup;
}
protected static PSignalTypes getSignalTypes(JTConnector conui)
{
PConnectorDescriptor d = conui.getConnectorDescriptor();
if (d == null && conui.getConnector() != null)
d = conui.getConnector().getDescriptor();
if (d == null) return null;
return d.getParentDescriptor().getModules().getDefinedSignals();
}
public ConnectorAction(String action, JTConnector connector, PSignal s)
{
this.connector = connector;
putValue(ACTION_COMMAND_KEY, action);
putValue(NAME, action);
this.signalType = s;
if (action == DISCONNECT)
{
setEnabled(connector.isConnected());
}
else
{
if (s == null)
{
setEnabled(false);
}
else
{
putValue(NAME, s.getName());
putValue(SMALL_ICON, renderIcon(s.getColor()));
}
}
}
private ImageIcon renderIcon(Color color)
{
BufferedImage bi = new BufferedImage(16,16,BufferedImage.TYPE_INT_RGB);
Graphics2D g2 = bi.createGraphics();
try
{
g2.setColor(color);
g2.fillRect(0, 0, 16, 16);
g2.setColor(Color.BLACK);
g2.drawRect(0, 0, 16-1, 16-1);
}
finally
{
g2.dispose();
}
return new ImageIcon(bi);
}
/*
private PSignalType toSignal(String name)
{
if (name == CAUDIO) return net.sf.nmedit.jpatch.clavia.nordmodular.Signal.AUDIO;
else if (name == CCTRL) return net.sf.nmedit.jpatch.clavia.nordmodular.Signal.CONTROL;
else if (name == CLOGIC) return net.sf.nmedit.jpatch.clavia.nordmodular.Signal.LOGIC;
else if (name == CSLAVE) return net.sf.nmedit.jpatch.clavia.nordmodular.Signal.SLAVE;
else if (name == CU1) return net.sf.nmedit.jpatch.clavia.nordmodular.Signal.USER1;
else if (name == CU2) return net.sf.nmedit.jpatch.clavia.nordmodular.Signal.USER2;
else return null;
}*/
public void actionPerformed(ActionEvent e)
{
if (!isEnabled())
return;
if (connector == null)
return;
String command = e.getActionCommand();
if (command == DISCONNECT)
disconnectCables();
else if (command == BREAK)
breakCables();
else if (command == DELETE)
deleteCables();
else
{
if (signalType != null)
setColor(signalType);
}
}
private void setColor(PSignal signal)
{
if (connector == null) return;
PConnector c = connector.getConnector();
if (c == null) return;
// for (Cable cable : connector.getConnectedCables()) {
// cable.setColor(signal.getColor());
// }
// connector.getCableManager().update(connector.getConnectedCables());
}
private void deleteCables()
{
if (connector == null) return;
PConnector c = connector.getConnector();
if (c != null && c.isConnected())
{
PUndoableEditSupport ues = c.getEditSupport();
try
{
if (ues != null)
ues.beginUpdate();
c.disconnect();
}
finally
{
if (ues != null)
ues.endUpdate();
}
}
}
private void breakCables()
{
if (connector == null) return;
PConnector c = connector.getConnector();
if (c != null) c.breakConnection();
}
private void disconnectCables()
{
deleteCables();
}
}
public NMPatch getPatch()
{
return patch;
}
public void helpForModule()
{
// TODO Auto-generated method stub
}
private static class ModuleContainerEventHandler extends MouseAdapter
implements PConnectionListener, ContainerListener
{
private JTModuleContainer cont;
public ModuleContainerEventHandler(JTModuleContainer cont)
{
this.cont = cont;
for (int i=cont.getComponentCount()-1;i>=0;i--)
{
Component c = cont.getComponent(i);
if (c instanceof JTModule)
installPopupListener((JTModule)c);
}
cont.addContainerListener(this);
}
public void uninstall()
{
// TODO
cont.getModuleContainer().getConnectionManager().removeConnectionListener(this);
}
public void adjustModuleColors()
{
/* TODO implement coloring algorithm
for (Cable cable: cont.getCableManager())
{
JTConnector a = cable.getSourceComponent();
JTConnector b = cable.getDestinationComponent();
if (a != null && b != null)
adjustColor(a, b);
} */
}
private void adjustColor(JTConnector a, JTConnector b)
{
/* TODO implement coloring algorithm
if (a.isOutput())
{
Signal s = b.getSignal();
if (s != null) a.getParent().setBackground(s.getColor());
}
if (b.isOutput())
{
Signal s = a.getSignal();
if (s != null) b.getParent().setBackground(s.getColor());
}
*/
}
public void componentAdded(ContainerEvent e)
{
Component c = e.getChild();
if (c instanceof JTModule)
installPopupListener((JTModule)c);
}
public void componentRemoved(ContainerEvent e)
{
Component c = e.getChild();
if (c instanceof JTModule)
uninstallPopupListener((JTModule)c);
}
protected void installPopupListener(JTModule m)
{
m.addMouseListener(this);
for (int i=m.getComponentCount()-1;i>=0;i--)
{
Component c = m.getComponent(i);
if (c instanceof JTConnector)
c.addMouseListener(this);
}
}
protected void uninstallPopupListener(JTModule m)
{
m.removeMouseListener(this);
for (int i=m.getComponentCount()-1;i>=0;i--)
{
Component c = m.getComponent(i);
if (c instanceof JTConnector)
c.removeMouseListener(this);
}
}
public void handlePopup(MouseEvent e)
{
if (!Platform.isPopupTrigger(e)) return;
Component c = e.getComponent();
if (c instanceof JTModule)
{
ModuleAction
.createPopup((JTModule)c)
.show(c, e.getX(), e.getY());
}
else if (c instanceof JTConnector)
{
ConnectorAction
.createPopup((JTConnector)c)
.show(c, e.getX(), e.getY());
}
}
public void mousePressed(MouseEvent e)
{
handlePopup(e);
}
public void mouseReleased(MouseEvent e)
{
handlePopup(e);
}
public void install()
{
cont.getModuleContainer().getConnectionManager().addConnectionListener(this);
}
public void connectionAdded(PConnectionEvent e)
{
JTConnector src = findConnector(cont, e.getSource());
JTConnector dst = findConnector(cont, e.getDestination());
if (src == null || dst == null)
return;
Cable cable = cont.getCableManager().createCable(src, dst);
PSignal signal = e.getSource().getSignalType();
if (signal != null)
{
cable.setColor(signal.getColor());
adjustColor(src, dst);
}
cont.getCableManager().add(cable);
}
public void connectionRemoved(PConnectionEvent e)
{
for (Cable cable : cont.getCableManager())
{
if (compareEq(cable.getSource(), cable.getDestination(), e.getSource(), e.getDestination()))
{
cont.getCableManager().remove(cable);
break;
}
}
}
private boolean compareEq(PConnector a1, PConnector b1, PConnector a2, PConnector b2)
{
return (a1==a2&&b1==b2)||(a1==b2&&b1==a2);
}
private static JTConnector findConnector(JTModuleContainer cont, PConnector c)
{
for (Component component: cont.getComponents())
{
if (component instanceof JTModule)
{
JTConnector jtc = findConnector((JTModule) component, c);
if (jtc != null)
return jtc;
}
}
return null;
}
private static JTConnector findConnector(JTModule module, PConnector c)
{
for (Component component: module.getComponents())
{
if (component instanceof JTConnector)
{
if (((JTConnector)component).getConnector() == c)
return (JTConnector) component;
}
}
return null;
}
}
public JTModuleContainer getPolyVoiceArea()
{
return (JTModuleContainer) spPoly.getViewport().getView();
}
public JTModuleContainer getCommonVoiceArea()
{
return (JTModuleContainer) spCommon.getViewport().getView();
}
private JScrollPane createVoiceArea(VoiceArea va) throws Exception
{
JTModuleContainer cont = getContext().createModuleContainer();
cont.setPatchContainer(this);
addModuleContainer(cont);
cont.setModuleContainer(va);
ModuleContainerEventHandler eh = new ModuleContainerEventHandler(cont);
if (va.isPolyVoiceArea()) ehPoly = eh; else ehCommon = eh;
eh.install();
populateVoiceArea(cont, va);
eh.adjustModuleColors();
JScrollPane scrollPane = new JScrollPane(cont);
scrollPane.getViewport().setScrollMode(JViewport.BLIT_SCROLL_MODE);
// scrollPane.setAutoscrolls(true);
scrollPane.getVerticalScrollBar().setUnitIncrement(10);
scrollPane.getHorizontalScrollBar().setUnitIncrement(10);
return scrollPane;
}
private void populateVoiceArea(JTModuleContainer cont, VoiceArea va) throws Exception
{
JTContext context = getContext();
int width = 0;
int height = 0;
// keep all connectors for later
Set<JTConnector> allConnectors = new HashSet<JTConnector>();
for (PModule module : va)
{
ModuleElement mstore = getContext().getStorageContext()
.getModuleStoreById(module.getComponentId());
if (mstore == null)
throw new RuntimeException("no store found for "+module);
//mstore.setStaticLayer(null);
JTModule jtmodule = mstore.createModule(context, module);
jtmodule.setLocation( module.getScreenX(), module.getScreenY() );
width = Math.max(width, jtmodule.getX()+jtmodule.getWidth());
height = Math.max(height, jtmodule.getY()+jtmodule.getHeight());
cont.add(jtmodule);
// keep connectors
allConnectors.addAll(jtmodule.getComponents(JTConnector.class));
/*
if (image == null)
{
image = jtmodule.renderStaticLayerImage();
mstore.setStaticLayer(image);
}
jtmodule.setStaticLayerBackingStore(image);*/
}
JTCableManager cm = cont.getCableManager();
Map<PConnector, JTConnector> connectorMap = new HashMap<PConnector, JTConnector>();
for (PConnection c: va.getConnectionManager())
{
PConnector pa = c.getA();
PConnector pb = c.getB();
JTConnector con1 = connectorMap.get(pa);
JTConnector con2 = connectorMap.get(pb);
if (con1 == null||con2 == null)
{
// search in map
for (JTConnector candidate: allConnectors)
{
if (candidate.getConnector() == pa)
{
con1 = candidate; // found
connectorMap.put(pa, candidate); // keep for later
if (con2 != null) break; // both were found
}
if (candidate.getConnector() == pb)
{
con2 = candidate; // found
connectorMap.put(pb, candidate); // keep for later
if (con1 != null) break; // both were found
}
}
}
if (con1 != null && con2 != null)
{
Cable cable = cm.createCable(con1, con2);
cable.setColor(c.getA().getSignalType().getColor());
cm.add(cable);
}
}
cont.revalidate();
}
public void dispose()
{
// TODO
settings.dispose();
ehPoly.uninstall();
ehCommon.uninstall();
}
public void propertyChange(PropertyChangeEvent evt)
{
if (NMSplitPane.STORED_SPLIT_PROPERTY == evt.getPropertyName())
{
int separator;
if (split.isTopHidden())
separator = Format.HEADER_SECTION_SEPARATOR_POSITION_TOP_MOST;
else if (split.isBottomHidden())
separator = Format.HEADER_SECTION_SEPARATOR_POSITION_BOTTOM_MOST;
else
{
separator = Math.max(1, Math.min(Format.HEADER_SECTION_SEPARATOR_POSITION_BOTTOM_MOST-1,
split.getStoredDividerLocation()));
}
patch.getHeader().setSeparatorPosition(separator);
}
}
// transferable
private static final String charset = "ISO-8859-1";
public static final DataFlavor nmPatchFlavor =
new DataFlavor(NMPatch.class, "Nord Modular patch 3.0");
private static final DataFlavor inputStreamFlavor =
new DataFlavor("text/plain; charset="+charset+"", "Nord Modular patch 3.0");
private static final DataFlavor uriFlavor =
new DataFlavor("text/uri-list; charset=utf-8", "uri list");
private static final DataFlavor imageFlavor =
DataFlavor.imageFlavor;
public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException
{
if (uriFlavor.equals(flavor))
{
File file = patch.getFile();
if (file == null)
throw new UnsupportedFlavorException(flavor);
return new ByteArrayInputStream(file.toURI().toString().getBytes());
}
if (inputStreamFlavor.equals(flavor))
{
ByteArrayOutputStream out = new ByteArrayOutputStream();
try
{
NmUtils.writePatch(patch, out);
}
catch (ParseException e)
{
throw new IOException(e.getMessage());
}
return new ByteArrayInputStream(out.toByteArray());
}
if (imageFlavor.equals(flavor))
{
return createPatchImageExporter().export();
}
if (nmPatchFlavor.equals(flavor))
{
//System.out.println("ask for patch " + DataTransferer.isFlavorCharsetTextType(flavor) + " class " + flavor.getRepresentationClass());
return patch;
}
throw new UnsupportedFlavorException(flavor);
}
public DataFlavor[] getTransferDataFlavors()
{
List<DataFlavor> flavorList = new ArrayList<DataFlavor>(2);
flavorList.add(inputStreamFlavor);
flavorList.add(imageFlavor);
if (patch.getFile() != null && patch.getFile().exists())
flavorList.add(uriFlavor);
flavorList.add(nmPatchFlavor);
return flavorList.toArray(new DataFlavor[flavorList.size()]);
}
public boolean isDataFlavorSupported(DataFlavor flavor)
{
for (DataFlavor f: getTransferDataFlavors())
{
if (f.equals(flavor))
return true;
}
return false;
}
public static JTNMPatch createPatchUI(NMPatch patch) throws Exception
{
JTNM1Context context = JTNM1Context.getCachedContext();
if (context == null)
return null;
synchronized(context.getLock())
{
return new JTNMPatch(context, patch);
}
}
}