Subversion Repositories svn.mios

Rev

Rev 662 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

/*
 * @(#)MidiDeviceRoutingGUI.java    beta8   2006/04/23
 *
 * Copyright (C) 2008    Adam King (adamjking@optusnet.com.au)
 *
 * This application 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.
 *
 * This application 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 application; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */


package org.midibox.midi.gui;

import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Observable;
import java.util.Observer;
import java.util.Vector;

import javax.sound.midi.MidiDevice;
import javax.swing.BorderFactory;
import javax.swing.DefaultListCellRenderer;
import javax.swing.DefaultListModel;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JToggleButton;
import javax.swing.ListSelectionModel;
import javax.swing.SwingConstants;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;

import org.midibox.midi.MidiDeviceRouting;
import org.midibox.utils.gui.DialogOwner;
import org.midibox.utils.gui.GuiUtils;
import org.midibox.utils.gui.ImageLoader;
import org.midibox.utils.gui.SimpleTransferHandler;

public class MidiDeviceRoutingGUI extends JPanel implements MouseListener,
        ChangeListener, ListSelectionListener, ActionListener, Observer {

    private MidiDeviceRouting midiDeviceRouting;

    private static Icon openIcon = ImageLoader.getImageIcon("midiPortOpen.png");

    private static Icon closedIcon = ImageLoader
            .getImageIcon("midiPortClosed.png");

    private Hashtable icons = new Hashtable();

    private DefaultListModel midiReadDevicesListModel;

    private JList midiReadDevicesList;

    private JScrollPane midiReadDevicesScroller;

    private DefaultListModel midiWriteDevicesListModel;

    private JList midiWriteDevicesList;

    private JScrollPane midiWriteDevicesScroller;

    private JPopupMenu connectionMenu;

    private JButton connectButton;

    private JButton disconnectButton;

    private JButton disconnectallButton;

    private JToggleButton releaseButton;

    private JPanel wirePanel;

    /*
     * private JDialog midiMapManagerDialog;
     */


    private Hashtable midiDevicePropertiesDialogs;

    public MidiDeviceRoutingGUI(MidiDeviceRouting midiDeviceRouting) {
        super(new BorderLayout());

        this.midiDeviceRouting = midiDeviceRouting;
        midiDeviceRouting.addObserver(this);

        midiDevicePropertiesDialogs = new Hashtable();

        addMouseListener(this);

        midiReadDevicesListModel = new DefaultListModel();
        midiWriteDevicesListModel = new DefaultListModel();

        midiReadDevicesList = new JList(midiReadDevicesListModel);
        midiReadDevicesList
                .setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
        midiReadDevicesList.setLayoutOrientation(JList.VERTICAL);
        midiReadDevicesList.setVisibleRowCount(6);
        midiReadDevicesList.setCellRenderer(new MyListCellRenderer());
        midiReadDevicesList.addMouseListener(this);
        midiReadDevicesList.setBackground(Color.WHITE);
        midiWriteDevicesList = new JList(midiWriteDevicesListModel);
        midiWriteDevicesList
                .setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
        midiWriteDevicesList.setLayoutOrientation(JList.VERTICAL);
        midiWriteDevicesList.setVisibleRowCount(6);
        midiWriteDevicesList.setCellRenderer(new MyListCellRenderer());
        midiWriteDevicesList.addMouseListener(this);
        midiWriteDevicesList.setBackground(Color.WHITE);

        JPanel listPane = new JPanel(new GridBagLayout());

        GridBagConstraints gbc = new GridBagConstraints();
        gbc.gridx = 1;
        gbc.gridy = 1;
        gbc.gridwidth = 1;
        gbc.gridheight = 1;
        gbc.insets = new Insets(2, 0, 2, 0);
        gbc.fill = GridBagConstraints.HORIZONTAL;
        gbc.weightx = 1.0;
        gbc.weighty = 0.0;

        listPane.setOpaque(false);

        listPane.add(new JLabel("MIDI Devices - Readable",
                SwingConstants.CENTER), gbc);
        gbc.gridy++;

        midiReadDevicesScroller = new JScrollPane(midiReadDevicesList);
        midiReadDevicesScroller.getViewport().addChangeListener(this);
        midiReadDevicesScroller
                .setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
        midiReadDevicesScroller
                .setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);

        gbc.fill = GridBagConstraints.BOTH;
        gbc.weighty = 1.0;
        gbc.weighty = 1.0;

        listPane.add(midiReadDevicesScroller, gbc);
        gbc.gridx++;

        wirePanel = new JPanel() {

            public void paintComponent(Graphics g) {
                super.paintComponent(g);
                Graphics2D g2d = (Graphics2D) g;
                g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                        RenderingHints.VALUE_ANTIALIAS_ON);

                Iterator it = getMidiDeviceRouting().getMidiReadDevices()
                        .iterator();

                int i = 0;

                while (it.hasNext()) {

                    MidiDevice transmittingDevice = (MidiDevice) it.next();

                    Iterator it2 = getMidiDeviceRouting().getMidiWriteDevices()
                            .iterator();

                    int o = 0;
                    while (it2.hasNext()) {
                        MidiDevice receivingDevice = (MidiDevice) it2.next();

                        int connected = getMidiDeviceRouting()
                                .devicesConnected(transmittingDevice,
                                        receivingDevice);

                        if (connected != MidiDeviceRouting.DISCONNECTED) {

                            Rectangle rec1 = midiReadDevicesList.getCellBounds(
                                    i, i);
                            Point p1 = new Point(0,
                                    ((rec1.height / 2) + rec1.y)
                                            + midiReadDevicesScroller.getY()
                                            - midiReadDevicesScroller
                                                    .getVerticalScrollBar()
                                                    .getValue()
                                            - wirePanel.getY());

                            Rectangle rec2 = midiWriteDevicesList
                                    .getCellBounds(o, o);
                            Point p2 = new Point(getWidth(),
                                    ((rec2.height / 2) + rec2.y)
                                            + midiReadDevicesScroller.getY()
                                            - midiWriteDevicesScroller
                                                    .getVerticalScrollBar()
                                                    .getValue()
                                            - wirePanel.getY());

                            if (connected == MidiDeviceRouting.LOGICALLY_CONNECTED) {
                                g2d.setStroke(new BasicStroke(2f,
                                        BasicStroke.CAP_BUTT,
                                        BasicStroke.JOIN_ROUND, 1f,
                                        new float[] { 3f, 6f }, 0f));
                            } else {
                                g2d.setStroke(new BasicStroke(2));
                            }
                            g2d.drawLine(p1.x, p1.y, p2.x, p2.y);
                        }
                        o++;
                    }
                    i++;
                }
            }
        };

        wirePanel.setOpaque(false);

        listPane.add(wirePanel, gbc);

        GuiUtils.setComponentSize(wirePanel, 80, 80);

        gbc.gridx++;
        gbc.gridy--;

        gbc.fill = GridBagConstraints.HORIZONTAL;
        gbc.weightx = 1.0;
        gbc.weighty = 0.0;

        listPane.add(new JLabel("MIDI Devices - Writeable",
                SwingConstants.CENTER), gbc);
        gbc.gridy++;

        midiWriteDevicesScroller = new JScrollPane(midiWriteDevicesList);
        midiWriteDevicesScroller
                .setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
        midiWriteDevicesScroller
                .setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
        midiWriteDevicesScroller.getViewport().addChangeListener(this);

        gbc.fill = GridBagConstraints.BOTH;
        gbc.weightx = 1.0;
        gbc.weighty = 1.0;

        listPane.add(midiWriteDevicesScroller, gbc);

        int maxPreferredWidth = Math.max(midiReadDevicesScroller
                .getPreferredSize().width, midiWriteDevicesScroller
                .getPreferredSize().width);
        int maxPreferredHeight = Math.max(midiReadDevicesScroller
                .getPreferredSize().height, midiWriteDevicesScroller
                .getPreferredSize().height);

        GuiUtils.setComponentSize(midiReadDevicesScroller, maxPreferredWidth,
                maxPreferredHeight);
        GuiUtils.setComponentSize(midiWriteDevicesScroller, maxPreferredWidth,
                maxPreferredHeight);

        midiReadDevicesList.setDragEnabled(true);
        midiWriteDevicesList.setDragEnabled(true);
        midiReadDevicesList.setTransferHandler(new MidiDevicesTransferHandler(
                false));
        midiWriteDevicesList.setTransferHandler(new MidiDevicesTransferHandler(
                true));
        midiReadDevicesList.addListSelectionListener(this);
        midiWriteDevicesList.addListSelectionListener(this);

        listPane.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
        listPane.setOpaque(false);
        add(listPane, BorderLayout.CENTER);

        JPanel buttonPane = new JPanel(new GridBagLayout());
        gbc = new GridBagConstraints();

        gbc.gridx = 0;
        gbc.gridy = 0;
        gbc.gridwidth = 1;
        gbc.fill = GridBagConstraints.BOTH;

        gbc.insets = new Insets(5, 5, 10, 5);

        connectButton = new JButton("Connect");
        connectButton.setToolTipText("Connect Selected Devices");
        connectButton.addActionListener(this);
        buttonPane.add(connectButton, gbc);
        gbc.gridx++;

        disconnectButton = new JButton("Disconnect");
        disconnectButton.setToolTipText("Disconnect Selected Devices");
        disconnectButton.addActionListener(this);
        buttonPane.add(disconnectButton, gbc);
        gbc.gridx++;

        disconnectallButton = new JButton("Disconnect All");
        disconnectallButton.setToolTipText("Disconnect All Devices");
        disconnectallButton.addActionListener(this);
        buttonPane.add(disconnectallButton, gbc);
        gbc.gridx++;

        releaseButton = new JToggleButton("Release Ports", midiDeviceRouting
                .isPortsReleased());
        releaseButton.setToolTipText("Release MIDI Ports");
        releaseButton.addActionListener(this);
        buttonPane.add(releaseButton, gbc);

        buttonPane.setOpaque(false);
        add(buttonPane, BorderLayout.SOUTH);
        populateTrees();
    }

    public MidiDeviceRouting getMidiDeviceRouting() {
        return midiDeviceRouting;
    }

    public void addMidiDeviceIcon(Object object, Icon icon) {
        icons.put(object, icon);
    }

    public Icon getMidiDeviceIcon(MidiDevice md) {
        if (icons.containsKey(md)) {
            return (Icon) icons.get(md);
        }

        if (icons.containsKey(md.getClass())) {
            return (Icon) icons.get(md.getClass());
        }

        try {
            if (md.isOpen()) {
                return openIcon;
            }

            return closedIcon;

        } catch (Exception e) {
            return closedIcon;
        }
    }

    public void redrawAll() {
        midiReadDevicesList.repaint();
        midiWriteDevicesList.repaint();
        wirePanel.repaint();
        setButtonStates();
    }

    private void buildConnectionMenu(boolean output) {

        final MidiDevice sourceDevice = (MidiDevice) ((output) ? midiDeviceRouting
                .getMidiWriteDevices()
                : midiDeviceRouting.getMidiReadDevices())
                .elementAt(((output) ? midiWriteDevicesList
                        : midiReadDevicesList).getSelectedIndex());
        final Vector targetDevices = ((output) ? midiDeviceRouting
                .getMidiReadDevices() : midiDeviceRouting.getMidiWriteDevices());

        connectionMenu = new JPopupMenu(sourceDevice.getDeviceInfo().getName());

        JMenu connectMenu = new JMenu("Connect to ...");

        connectionMenu.add(connectMenu);

        connectionMenu.setLabel(sourceDevice.getDeviceInfo().getName());

        Iterator it = targetDevices.iterator();

        while (it.hasNext()) {

            MidiDevice targetDevice = (MidiDevice) it.next();

            final MidiDevice transmittingDevice = ((output) ? targetDevice
                    : sourceDevice);
            final MidiDevice receivingDevice = ((output) ? sourceDevice
                    : targetDevice);
            final JCheckBoxMenuItem connect = new JCheckBoxMenuItem(
                    targetDevice.getDeviceInfo().getName(),
                    midiDeviceRouting.devicesConnected(transmittingDevice,
                            receivingDevice) != MidiDeviceRouting.DISCONNECTED);

            connect.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent ae) {
                    if (connect.isSelected()) {
                        midiDeviceRouting.connectDevices(transmittingDevice,
                                receivingDevice);
                    } else {
                        midiDeviceRouting.disconnectDevices(transmittingDevice,
                                receivingDevice);
                    }
                }
            });
            connectMenu.add(connect);
        }

        JMenuItem properties = new JMenuItem("MIDI Device Properties");
        properties.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent ae) {
                showMidiDevicePropertyDialog(sourceDevice);
            }
        });
        connectionMenu.add(properties);
    }

    private void showMidiDevicePropertyDialog(MidiDevice midiDeviceIn) {

        final MidiDevice midiDevice = midiDeviceIn;

        if (!midiDevicePropertiesDialogs.containsKey(midiDevice)) {

            final MidiDeviceProperties midiDeviceProperties = new MidiDeviceProperties(
                    midiDevice);

            JDialog midiDevicePropertiesDialog = new JDialog(DialogOwner
                    .getFrame(), midiDevice.getDeviceInfo().getName(), false);
            midiDevicePropertiesDialog.setContentPane(midiDeviceProperties);
            midiDevicePropertiesDialog.pack();
            midiDevicePropertiesDialog.setLocationRelativeTo(this);
            midiDevicePropertiesDialog.setVisible(true);

            midiDevicePropertiesDialogs.put(midiDevice,
                    midiDevicePropertiesDialog);

            midiDevicePropertiesDialog.addWindowListener(new WindowAdapter() {
                public void windowClosing(WindowEvent we) {
                    midiDevicePropertiesDialogs.remove(midiDevice);
                }
            });
        }
        ((JDialog) midiDevicePropertiesDialogs.get(midiDevice)).requestFocus();
    }

    private void populateTrees() {

        midiReadDevicesListModel.removeAllElements();

        Iterator it = midiDeviceRouting.getMidiReadDevices().iterator();

        while (it.hasNext()) {
            midiReadDevicesListModel.addElement(((MidiDevice) it.next())
                    .getDeviceInfo().getName());
        }

        midiReadDevicesList.setSelectedIndex(0);

        midiWriteDevicesListModel.removeAllElements();

        it = midiDeviceRouting.getMidiWriteDevices().iterator();

        while (it.hasNext()) {
            midiWriteDevicesListModel.addElement(((MidiDevice) it.next())
                    .getDeviceInfo().getName());
        }

        midiWriteDevicesList.setSelectedIndex(0);

        updateUI();
    }

    private void setButtonStates() {

        int[] selectedReadIndices = midiReadDevicesList.getSelectedIndices();
        int[] selectedWriteIndices = midiWriteDevicesList.getSelectedIndices();

        connectButton.setEnabled(false);
        disconnectButton.setEnabled(false);
        releaseButton.setSelected(midiDeviceRouting.isPortsReleased());

        if (selectedReadIndices.length > 0 && selectedWriteIndices.length > 0) {

            for (int r = 0; r < selectedReadIndices.length; r++) {

                MidiDevice transmittingDevice = (MidiDevice) midiDeviceRouting
                        .getMidiReadDevices().elementAt(selectedReadIndices[r]);

                for (int w = 0; w < selectedWriteIndices.length; w++) {

                    MidiDevice receivingDevice = (MidiDevice) midiDeviceRouting
                            .getMidiWriteDevices().elementAt(
                                    selectedWriteIndices[w]);

                    if (midiDeviceRouting.devicesConnected(transmittingDevice,
                            receivingDevice) != MidiDeviceRouting.DISCONNECTED) {
                        disconnectButton.setEnabled(true);
                    } else {
                        connectButton.setEnabled(true);
                    }
                }
            }
        }
    }

    public void valueChanged(ListSelectionEvent lse) {
        setButtonStates();
    }

    public void mouseClicked(MouseEvent me) {

    }

    public void mouseReleased(MouseEvent me) {
        Object source = me.getSource();

        if (source == midiReadDevicesList || source == midiWriteDevicesList) {
            JList source2 = (JList) source;
            if (me.getButton() == MouseEvent.BUTTON3) {
                int index = source2.locationToIndex(me.getPoint());
                source2.setSelectedIndex(index);
                buildConnectionMenu((source == midiWriteDevicesList) ? true
                        : false);
                connectionMenu.show(source2, me.getX(), me.getY());
            }
        }
    }

    public void mousePressed(MouseEvent me) {

    }

    public void mouseEntered(MouseEvent me) {

    }

    public void mouseExited(MouseEvent me) {

    }

    public void stateChanged(ChangeEvent ce) {
        wirePanel.repaint();
    }

    public Dimension getMinimumSize() {
        return getPreferredSize();
    }

    public void update(Observable observable, Object object) {
        if (observable == midiDeviceRouting) {
            if (object == midiDeviceRouting.getMidiReadDevices()
                    || object == midiDeviceRouting.getMidiWriteDevices()) {
                populateTrees();
            }
            redrawAll();
        }
    }

    public void actionPerformed(ActionEvent ae) {
        Object source = ae.getSource();

        if (source == connectButton) {

            int[] selectedReadIndices = midiReadDevicesList
                    .getSelectedIndices();
            int[] selectedWriteIndices = midiWriteDevicesList
                    .getSelectedIndices();

            for (int r = 0; r < selectedReadIndices.length; r++) {

                MidiDevice transmittingDevice = (MidiDevice) midiDeviceRouting
                        .getMidiReadDevices().elementAt(selectedReadIndices[r]);

                for (int w = 0; w < selectedWriteIndices.length; w++) {
                    MidiDevice receivingDevice = (MidiDevice) midiDeviceRouting
                            .getMidiWriteDevices().elementAt(
                                    selectedWriteIndices[w]);

                    midiDeviceRouting.connectDevices(transmittingDevice,
                            receivingDevice);
                }

            }
        } else if (source == disconnectButton) {

            int[] selectedReadIndices = midiReadDevicesList
                    .getSelectedIndices();
            int[] selectedWriteIndices = midiWriteDevicesList
                    .getSelectedIndices();

            for (int r = 0; r < selectedReadIndices.length; r++) {

                MidiDevice transmittingDevice = (MidiDevice) midiDeviceRouting
                        .getMidiReadDevices().elementAt(selectedReadIndices[r]);

                for (int w = 0; w < selectedWriteIndices.length; w++) {
                    MidiDevice receivingDevice = (MidiDevice) midiDeviceRouting
                            .getMidiWriteDevices().elementAt(
                                    selectedWriteIndices[w]);

                    midiDeviceRouting.disconnectDevices(transmittingDevice,
                            receivingDevice);
                }
            }
        } else if (source == disconnectallButton) {
            midiDeviceRouting.disconnectAll();
        } else if (source == releaseButton) {
            midiDeviceRouting.setPortsReleased(releaseButton.isSelected());
        }
    }

    public class MyListCellRenderer extends DefaultListCellRenderer {
        public Component getListCellRendererComponent(JList list, Object value,
                int index, boolean isSelected, boolean hasFocus) {

            super.getListCellRendererComponent(list, value, index, isSelected,
                    hasFocus);

            setIcon(getMidiDeviceIcon(((MidiDevice) ((list == midiWriteDevicesList) ? midiDeviceRouting
                    .getMidiWriteDevices()
                    : midiDeviceRouting.getMidiReadDevices()).elementAt(index))));

            return this;
        }
    }

    public class MidiDevicesTransferHandler extends SimpleTransferHandler {

        private boolean output;

        public MidiDevicesTransferHandler(boolean output) {
            super();
            this.output = output;
            dropOnSelf = false;
            supportedDataFlavors = new DataFlavor[] { MidiDeviceTransferable.LOCAL_JVM_MIDI_DEVICE_FLAVOR };
        }

        public boolean importData(JComponent c, Transferable t) {

            if (canImport(c, supportedDataFlavors)) {

                int[] indices = ((output) ? midiWriteDevicesList
                        : midiReadDevicesList).getSelectedIndices();

                for (int d = 0; d < indices.length; d++) {

                    MidiDevice localDevice = (MidiDevice) ((output) ? midiDeviceRouting
                            .getMidiWriteDevices()
                            : midiDeviceRouting.getMidiReadDevices())
                            .elementAt(indices[d]);

                    try {
                        MidiDevice[] remoteDevices = (MidiDevice[]) t
                                .getTransferData(MidiDeviceTransferable.LOCAL_JVM_MIDI_DEVICE_FLAVOR);

                        for (int d2 = 0; d2 < remoteDevices.length; d2++) {

                            MidiDevice remoteDevice = remoteDevices[d2];

                            if (output) {
                                midiDeviceRouting.connectDevices(remoteDevice,
                                        localDevice);
                            } else {
                                midiDeviceRouting.connectDevices(localDevice,
                                        remoteDevice);
                            }
                        }
                    } catch (Exception e) {
                        return false;
                    }
                }
                return true;
            }
            return false;
        }

        protected Transferable createTransferable(JComponent c) {
            justExported = true;
            int[] indices = ((output) ? midiWriteDevicesList
                    : midiReadDevicesList).getSelectedIndices();

            MidiDevice[] midiDevices = new MidiDevice[indices.length];

            for (int d = 0; d < midiDevices.length; d++) {
                midiDevices[d] = (MidiDevice) ((output) ? midiDeviceRouting
                        .getMidiWriteDevices() : midiDeviceRouting
                        .getMidiReadDevices()).elementAt(indices[d]);
            }
            return new MidiDeviceTransferable(midiDevices);
        }
    }
}