/*
Mjdj MIDI Morph - an extensible MIDI processor and translator.
Copyright (C) 2010 Confusionists, LLC (www.confusionists.com)
This program 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 3 of the License, or (at your option) any later version. This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
You may contact the author at mjdj_midi_morph [at] confusionists.com
*/
package com.confusionists.mjdj;
import java.io.File;
import java.util.*;
import java.util.List;
import java.util.Timer;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowEvent;
import java.io.IOException;
import javax.swing.*;
import javax.swing.border.TitledBorder;
import org.codehaus.groovy.runtime.StackTraceUtils;
import com.confusionists.mjdj.fileIO.MorphLoaderGroovy;
import com.confusionists.mjdj.fileIO.MorphLoaderJava;
import com.confusionists.mjdj.midi.MjdjServiceImpl;
import com.confusionists.mjdj.morphs.nullConnection.NullConnection;
import com.confusionists.mjdj.settings.MorphAdaptor;
import com.confusionists.mjdj.settings.Settings;
import com.confusionists.mjdj.ui.*;
import com.confusionists.mjdjApi.midiDevice.DeviceWrapper;
import com.confusionists.mjdjApi.morph.DeviceNotFoundException;
import com.confusionists.mjdjApi.morph.Morph;
import com.confusionists.mjdjApi.util.MjdjService;
import com.confusionists.swing.JFrameRedux;
import com.confusionists.swing.SwingOps;
@SuppressWarnings("serial")
public class Main extends JFrameRedux {
public static final String PRODUCT_NAME = "Mjdj MIDI Morph";
public static final String PRODUCT_VERSION = "Beta 0.2.00i";
public static boolean isDevelopment = false;
public MorphCheckboxList morphCheckboxList;
JTextArea outputArea;
JButton lockButton;
MidiDeviceCheckboxList inputList;
MidiDeviceCheckboxList outputList;
JMenu jMenuTools = new JMenu();
JToolBar toolBar = new JToolBar();
ClockSourceCombo clockSourceCombo = new ClockSourceCombo();
public JToggleButton debugToggle;
private Timer logTimer = new Timer();
private StringBuffer toLog = new StringBuffer();
private boolean inited = false;
public static void main(String[] args) {
try {
if (args.length > 0 && args[0].equals("development"))
isDevelopment = true;
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
System.setProperty("apple.laf.useScreenMenuBar", "true");
com.confusionists.mjdj.Main frame = Universe.instance.main = new Main();
frame.setSize(1024, 768);
frame.setTitle(PRODUCT_NAME + " " + PRODUCT_VERSION);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setup();
frame.setVisible(true);
} catch (Throwable e) {
e = StackTraceUtils.deepSanitize(e);
JOptionPane.showMessageDialog(null, "Mjdj cannot start, sorry\n(" + e + ")", "Mjdj", JOptionPane.OK_OPTION);
System.out.println("There has been an error, exiting.");
System.err.println(e.getMessage());
e.printStackTrace();
System.exit(-1);
}
}
public void logInner(String text, boolean linefeed) {
String line;
if (linefeed) {
line = text + "\n";
} else {
line = text;
}
toLog.append(line);
}
public void logTimerRun() {
if (toLog.length() == 0 || !inited)
return;
String text = toLog.toString();
toLog.delete(0, text.length());
outputArea.setText(outputArea.getText() + text);
outputArea.setCaretPosition(outputArea.getText().length());
System.out.println(text);
}
/** File | Exit action performed */
@Override
public void jMenuFileExit_actionPerformed(ActionEvent e) {
super.jMenuFileExit_actionPerformed(e);
onClose();
}
@Override
public void windowClosing(WindowEvent e) {
super.windowClosing(e);
onClose();
}
boolean alreadyClosed = false;
public void onClose() {
if (alreadyClosed)
return;
try {
alreadyClosed = true;
for (MorphAdaptor morph : morphCheckboxList.getMorphs()) {
morph.getMorph().shutdown();
morph.saveMorphSettings();
}
for (DeviceWrapper device : Universe.instance.midiDriverManager.inputTransmitters) {
System.out.println("closing " + device.getName());
device.close();
}
for (DeviceWrapper device : Universe.instance.midiDriverManager.outputReceivers) {
System.out.println("closing " + device.getName());
device.close();
}
Settings.getInstance().save();
logTimer.cancel();
} catch (IOException e1) {
System.err.println("Error saving settings " + e1.getMessage());
}
}
public void setup() throws Exception {
OSXAdapter.setQuitHandler(this, getClass().getDeclaredMethod("onClose", (Class[]) null));
logTimer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
Universe.instance.main.logTimerRun();
}
}, 0, 100);
toolBar.add(Universe.instance.syncButton);
toolBar.add(Box.createHorizontalStrut(5));
JLabel label;
label = new JLabel("Clock Source");
toolBar.add(label);
this.getContentPane().add(toolBar, BorderLayout.NORTH);
toolBar.add(clockSourceCombo);
toolBar.add(Box.createHorizontalGlue());
lockButton = new JButton("Lock");
toolBar.add(lockButton);
lockButton.addActionListener(new java.awt.event.ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
Main.this.flipScreenProtector();
}
});
toolBar.add(Box.createHorizontalGlue());
JButton button;
button = new JButton("Move Morph Up");
toolBar.add(button);
button.addActionListener(new java.awt.event.ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
MorphCheckboxList.instance.moveSelectedMorph(true);
}
});
button = new JButton("Move Morph Down");
toolBar.add(button);
button.addActionListener(new java.awt.event.ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
MorphCheckboxList.instance.moveSelectedMorph(false);
}
});
toolBar.add(Box.createHorizontalGlue());
toolBar.add(Box.createHorizontalGlue());
button = new JButton("Reload Morphs");
button.addActionListener(new java.awt.event.ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
rescanMorphs();
}
});
toolBar.add(button);
button = new JButton("Clear Output");
button.addActionListener(new java.awt.event.ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
outputArea.setText("");
outputArea.setCaretPosition(0);
}
});
toolBar.add(button);
debugToggle = new JToggleButton("Debug");
toolBar.add(debugToggle);
outputArea = new JTextArea();
JScrollPane scroller = new JScrollPane();
scroller.getViewport().add(outputArea);
SwingOps.setAllSizes(scroller, new Dimension(100, 200));
getContentPane().add(scroller, BorderLayout.SOUTH);
jMenuTools.setText("Tools");
JMenuItem menuItem;
menuItem = new JMenuItem();
menuItem.setText("Rescan MIDI");
menuItem.addActionListener(new java.awt.event.ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
rescanMidi();
}
});
// broken
jMenuTools.add(menuItem);
menuItem = new JMenuItem();
menuItem.setText("Rescan Morphs");
menuItem.addActionListener(new java.awt.event.ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
rescanMorphs();
}
});
jMenuTools.add(menuItem);
menuItem = new JMenuItem();
menuItem.setText("Compile Java Morphs (and reload)");
menuItem.addActionListener(new java.awt.event.ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
rescanMorphs();
}
});
jMenuTools.add(menuItem);
menuItem = new JMenuItem();
menuItem.setText("Diagnostics");
menuItem.addActionListener(new java.awt.event.ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
runDiagnostics();
}
});
jMenuTools.add(menuItem);
jMenuBar1.add(jMenuTools);
JMenu menuHelp = new JMenu("Help");
jMenuBar1.add(menuHelp);
boolean aboutMac = OSXAdapter.setAboutHandler(this, getClass().getDeclaredMethod("onAboutClick", (Class[]) null));
if (aboutMac) {
// we need to suppress the File menu FileMenu
jMenuBar1.remove(jMenuFile);
} else {
JMenuItem menuAbout = new JMenuItem("About Mjdj MIDI Morph");
menuAbout.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
Main.this.onAboutClick();
}
});
menuHelp.add(menuAbout);
}
inputList = new MidiDeviceCheckboxList("Input MIDI devices");
scroller = getTitledScroller(inputList, inputList.getName());
scroller.setMinimumSize(new Dimension(350, 50));
scroller.setPreferredSize(new Dimension(350, 100));
getContentPane().add(scroller, BorderLayout.WEST);
outputList = new MidiDeviceCheckboxList("Ouput MIDI devices");
scroller = getTitledScroller(outputList, outputList.getName());
scroller.setMinimumSize(new Dimension(350, 50));
scroller.setPreferredSize(new Dimension(350, 100));
getContentPane().add(scroller, BorderLayout.EAST);
morphCheckboxList = new MorphCheckboxList();
scroller = getTitledScroller(morphCheckboxList, "MIDI Morphs (right-click to open)");
getContentPane().add(scroller, BorderLayout.CENTER);
// first scan
rescanMidi();
rescanMorphs();
Logger.log(this.getTitle() + " starting, JVM: " + System.getProperty("java.version")
+ ".\nNote: this log is asynchronous (NOT in realtime) so using debug should not affect performance (much).");
setLocked(Settings.getInstance().lockScreen);
inited = true;
}
public void onAboutClick() {
AboutBox dlg = new AboutBox(this);
Dimension dlgSize = dlg.getPreferredSize();
Dimension frmSize = getSize();
Point loc = getLocation();
dlg.setLocation((frmSize.width - dlgSize.width) / 2 + loc.x, (frmSize.height - dlgSize.height) / 2 + loc.y);
dlg.setModal(true);
dlg.pack();
dlg.setSize(new Dimension(600, 250));
dlg.setVisible(true);
dlg.setResizable(false);
}
private static JScrollPane getTitledScroller(JComponent wrapThis, String title) {
JScrollPane scroller = new JScrollPane();
scroller.getViewport().add(wrapThis);
scroller.setBorder(new TitledBorder(title));
return scroller;
}
protected void runDiagnostics() {
System.gc();
// get all translators
Logger.log("Found " + morphCheckboxList.getMorphs().size() + " Morphers");
List<MorphAdaptor> list = morphCheckboxList.getMorphs();
for (MorphAdaptor morph : list) {
Logger.log("Found " + morph.getMorph().getName() + " and is active? " + morph.isActive());
String text = morph.getMorph().diagnose();
if (text != null)
Logger.log(text);
}
Logger.log("");
Logger.log("Found " + inputList.getWrappers().size() + " Input Transmitters");
List<? extends DeviceWrapper> list2 = inputList.getWrappers();
for (DeviceWrapper wrapper : list2) {
Logger.log("'" + wrapper + "'" + " - active=" + wrapper.isActive());
}
Logger.log("");
Logger.log("Found " + outputList.getWrappers().size() + " Input Transmitters");
List<? extends DeviceWrapper> list3 = outputList.getWrappers();
for (DeviceWrapper wrapper : list3) {
Logger.log("'" + wrapper + "'" + " - active=" + wrapper.isActive());
}
Universe.instance.clockHandler.diagnose();
}
public void restartApplication() throws Exception
{
// final String javaBin = System.getProperty("java.home") + File.separator + "bin" + File.separator + "java";
// final File currentJar = new File(this.class.getProtectionDomain().getCodeSource().getLocation().toURI());
//
// /* is it a jar file? */
// if(!currentJar.getName().endsWith(".jar"))
// return;
//
// /* Build command: java -jar application.jar */
// final ArrayList<String> command = new ArrayList<String>();
// command.add(javaBin);
// command.add("-jar");
// command.add(currentJar.getPath());
//
// final ProcessBuilder builder = new ProcessBuilder(command);
// builder.start();
System.exit(605);
}
public void rescanMidi() {
Universe.instance.midiDriverManager.close();
Universe.instance.midiDriverManager.init();
inputList.setList(Universe.instance.midiDriverManager.inputTransmitters);
clockSourceCombo.setList(Universe.instance.midiDriverManager.inputTransmitters);
outputList.setListReceivers(Universe.instance.midiDriverManager.outputReceivers);
}
public void rescanMorphs() {
morphCheckboxList.setListData(new Vector<MorphCheckbox>());
MjdjService serviceImpl = new MjdjServiceImpl();
try {
List<Morph> morphs = new ArrayList<Morph>();
MorphLoaderJava loader = new MorphLoaderJava();
loader.load(morphs);
MorphLoaderGroovy loaderGroovy = new MorphLoaderGroovy();
loaderGroovy.load(morphs);
morphs.add(new NullConnection()); // this is the one morph that is actually compiled in to Mjdj
for (Morph morph : morphs) {
String morphName = "Unknown";
try {
// plug it into the adaptor that's been loaded, or get a new one
MorphAdaptor morphAdaptor = Settings.getInstance().getMorphAdaptor(morph);
morphAdaptor.setDead(true); // how we keep track of whether it ever
// made it...
morphName = morph.getName();
if (morphName != null) {
morph.setService(serviceImpl);
morph.setInDeviceNames(inputList.getNames());
morph.setOutDeviceNames(outputList.getNames());
morph.init();
morphAdaptor.restore();
morphAdaptor.setDead(false);
} else {
Logger.log("Morph class " + morph.getClass().getName() + " has no name set!");
}
} catch (DeviceNotFoundException e) {
Logger.log("Morph '" + morphName + "' not loaded, requires device '" + e.deviceName + "'");
} catch (Exception e2) {
Logger.log("Unknown problem loading morph '" + morphName + "', see stack trace");
e2.printStackTrace();
}
}
morphCheckboxList.setMorphs(morphs);
Logger.log("Sucessfully loaded " + morphs.size() + " translators");
} catch (Exception e) {
Logger.log("Problems loading Java translators ");
throw new RuntimeException(e);
}
}
private void setLocked(boolean locked) {
if (!locked)
this.lockButton.setText("Lock");
else
this.lockButton.setText("Unlock");
this.morphCheckboxList.setEnabled(!locked);
this.inputList.setEnabled(!locked);
this.outputList.setEnabled(!locked);
Settings.getInstance().lockScreen = locked;
}
public void flipScreenProtector() {
boolean enable = !this.morphCheckboxList.isEnabled();
setLocked(!enable);
}
}