us.physion.ovation.ui.editor.ResourceInfoPanel.java Source code

Java tutorial

Introduction

Here is the source code for us.physion.ovation.ui.editor.ResourceInfoPanel.java

Source

/*
 * Copyright (C) 2014 Physion LLC
 *
 * 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/>.
 */
package us.physion.ovation.ui.editor;

import com.google.common.base.Splitter;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ItemEvent;
import java.awt.geom.Area;
import java.awt.geom.Rectangle2D;
import java.awt.geom.RoundRectangle2D;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.border.AbstractBorder;
import javax.swing.text.AbstractDocument;
import javax.swing.text.StyleConstants;
import org.jdesktop.swingx.autocomplete.AutoCompleteDecorator;
import org.netbeans.api.progress.ProgressHandle;
import org.netbeans.api.progress.ProgressHandleFactory;
import org.openide.util.Lookup;
import org.openide.util.NbBundle.Messages;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import us.physion.ovation.DataContext;
import us.physion.ovation.domain.AnalysisRecord;
import us.physion.ovation.domain.Epoch;
import us.physion.ovation.domain.Measurement;
import us.physion.ovation.domain.OvationEntity;
import us.physion.ovation.domain.Source;
import us.physion.ovation.domain.Resource;
import us.physion.ovation.domain.Revision;
import us.physion.ovation.exceptions.OvationException;
import us.physion.ovation.ui.browser.BrowserUtilities;
import us.physion.ovation.ui.interfaces.ConnectionProvider;
import us.physion.ovation.ui.interfaces.EntityColors;
import us.physion.ovation.ui.interfaces.EventQueueUtilities;
import us.physion.ovation.ui.reveal.api.RevealNode;

/**
 *
 * @author barry
 */
@Messages({ "Adding_source=Adding Source {0}", "Resource_Multiple_Content_Types=<Multiple>",
        "ResourceInfoPanel_Drop_Files_For_New_Revision=Drop files to create new revision" })
public class ResourceInfoPanel extends javax.swing.JPanel {

    private static final String ELEM = AbstractDocument.ElementNameAttribute;
    private static final String ICON = StyleConstants.IconElementName;
    private static final String COMP = StyleConstants.ComponentElementName;

    private final Set<? extends OvationEntity> elements;

    /**
     * Creates new form ResourceInfoPanel
     */
    public ResourceInfoPanel(Iterable<? extends OvationEntity> elements) {
        this.elements = ImmutableSet.copyOf(elements);

        initComponents();

        initUi();
    }

    private void initUi() {
        DefaultComboBoxModel<String> model = new DefaultComboBoxModel<>(
                getAvailableContentTypes().toArray(new String[0]));
        contentTypeComboBox.setModel(model);
        contentTypeComboBox.setSelectedItem(getContentType());

        contentTypeComboBox.addItemListener((ItemEvent e) -> {
            if (e.getStateChange() == ItemEvent.SELECTED) {
                String selection = (String) e.getItem();
                setContentType(selection);
            }
        });

        final DataContext ctx = Lookup.getDefault().lookup(ConnectionProvider.class).getDefaultContext();

        addSourcesTextField.addActionListener((ActionEvent e) -> {
            addSourceFromText(ctx, addSourcesTextField.getText());
        });

        addSourcesTextField.setEnabled(getMeasurements().size() > 0 || getResources().stream().anyMatch((r) -> {
            return !(r instanceof Measurement);
        }) || getRevisions().stream().anyMatch((r) -> {
            return !(r.getResource() instanceof Measurement);
        }));

        OvationEntity e = Iterables.getFirst(getEntities(OvationEntity.class), null);
        if (e != null) {
            ListeningExecutorService svc = e.getDataContext().getCoordinator().getExecutorService();
            ListenableFuture<List<String>> sourceIds = svc.submit(() -> {
                try {
                    //TODO make this async
                    List<String> sourceIds1 = getSourceIds(ctx.getTopLevelSources());
                    List<String> sortedIds = Lists.newArrayList(sourceIds1);
                    Collections.sort(sortedIds);
                    AutoCompleteDecorator.decorate(addSourcesTextField, sortedIds, false);
                    return sortedIds;
                } catch (Throwable ex) {
                    logger.error("Unable to retrieve Sources. Autocomplete for Source IDs disabled.");
                    return Lists.newArrayList();
                }
            });
        }

        revisionFileWell
                .setDelegate(new FileWell.AbstractDelegate(Bundle.ResourceInfoPanel_Drop_Files_For_New_Revision()) {

                    @Override
                    public void filesDropped(File[] files) {
                        if (files.length == 0 || getResources().size() > 1) {
                            return;
                        }

                        for (Resource r : getResources()) {
                            try {
                                File main = null;
                                List<URL> supporting = Lists.newLinkedList();
                                for (File f : files) {
                                    if (f.getName().equals(r.getFilename())) {
                                        main = f;
                                    } else {
                                        supporting.add(f.toURI().toURL());
                                    }

                                }

                                if (main == null) {
                                    main = files[0];
                                    supporting.remove(0);
                                }

                                r.addRevision(main.toURI().toURL(), ContentTypes.getContentType(main),
                                        main.getName(), supporting);

                            } catch (MalformedURLException ex) {
                                throw new OvationException("Unable to create new revision", ex);
                            } catch (IOException ex) {
                                throw new OvationException("Unable to create new revision", ex);
                            }
                        }
                    }
                });

        updateInputs();
    }

    private List<String> getSourceIds(Iterable<Source> roots) {
        List<String> result = Lists.newLinkedList();
        for (Source s : roots) {
            result.add(s.getIdentifier());
            result.addAll(getSourceIds(s.getChildren()));
        }

        return result;
    }

    public final String getContentType() {
        List<String> contentTypes = Lists.newLinkedList();
        for (Resource e : getEntities(Resource.class)) {
            contentTypes.add(e.getDataContentType());
        }

        if (contentTypes.size() == 1) {
            return contentTypes.get(0);
        } else {
            return Bundle.Resource_Multiple_Content_Types();
        }
    }

    public void setContentType(String contentType) {
        if (Bundle.Resource_Multiple_Content_Types().equals(contentType)) {
            return;
        }

        for (Resource e : getEntities(Resource.class)) {
            if (!e.getDataContentType().equals(contentType)) {
                e.setDataContentType(contentType);
            }
        }
    }

    public Set<? extends OvationEntity> getElements() {
        return elements;
    }

    public final List<String> getAvailableContentTypes() {
        List<String> contentTypes = ContentTypes.getContentTypes();
        contentTypes.add("application/octet-stream");

        if (!contentTypes.contains(getContentType())) {
            contentTypes.add(getContentType());
        }

        Collections.sort(contentTypes);

        return contentTypes;
    }

    <T extends OvationEntity> Iterable<T> getEntities(final Class<T> cls) {
        return Iterables.transform(Iterables.filter(getElements(), (OvationEntity t) -> {
            if (t == null) {
                return false;
            }

            return cls.isAssignableFrom(t.getClass());
        }), (OvationEntity f) -> (T) f);
    }

    final Set<Measurement> getMeasurements() {
        return ImmutableSet.copyOf(getEntities(Measurement.class));
    }

    final Set<Resource> getResources() {
        return ImmutableSet.copyOf(getEntities(Resource.class));
    }

    final Set<Revision> getRevisions() {
        return ImmutableSet.copyOf(getEntities(Revision.class));
    }

    /**
     * This method is called from within the constructor to initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is always
     * regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        metadataPanel = new javax.swing.JPanel();
        addInputsLabel = new javax.swing.JLabel();
        addSourcesTextField = new javax.swing.JTextField();
        jScrollPane1 = new javax.swing.JScrollPane();
        inputsTextPane = new javax.swing.JTextPane();
        formatLabel = new javax.swing.JLabel();
        contentTypeComboBox = new javax.swing.JComboBox();
        revisionFileWell = new us.physion.ovation.ui.editor.FileWell();

        setBackground(javax.swing.UIManager.getDefaults().getColor("EditorPane.background"));
        setLayout(new java.awt.BorderLayout());

        metadataPanel.setBackground(javax.swing.UIManager.getDefaults().getColor("EditorPane.background"));
        metadataPanel.setBorder(javax.swing.BorderFactory.createTitledBorder(org.openide.util.NbBundle
                .getMessage(ResourceInfoPanel.class, "ResourceInfoPanel.metadataPanel.border.title"))); // NOI18N

        org.openide.awt.Mnemonics.setLocalizedText(addInputsLabel, org.openide.util.NbBundle
                .getMessage(ResourceInfoPanel.class, "ResourceInfoPanel.addInputsLabel.text")); // NOI18N

        addSourcesTextField.setToolTipText(org.openide.util.NbBundle.getMessage(ResourceInfoPanel.class,
                "ResourceInfoPanel.addSourcesTextField.toolTipText")); // NOI18N
        addSourcesTextField.setBorder(new javax.swing.border.LineBorder(
                javax.swing.UIManager.getDefaults().getColor("InternalFrame.background"), 1, true));

        jScrollPane1.setBorder(null);

        inputsTextPane.setBorder(null);
        jScrollPane1.setViewportView(inputsTextPane);

        org.openide.awt.Mnemonics.setLocalizedText(formatLabel, org.openide.util.NbBundle
                .getMessage(ResourceInfoPanel.class, "ResourceInfoPanel.formatLabel.text")); // NOI18N

        contentTypeComboBox.setModel(
                new javax.swing.DefaultComboBoxModel(new String[] { "Item 1", "Item 2", "Item 3", "Item 4" }));

        javax.swing.GroupLayout metadataPanelLayout = new javax.swing.GroupLayout(metadataPanel);
        metadataPanel.setLayout(metadataPanelLayout);
        metadataPanelLayout.setHorizontalGroup(metadataPanelLayout
                .createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                .addGroup(metadataPanelLayout.createSequentialGroup().addContainerGap()
                        .addGroup(metadataPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                                .addComponent(jScrollPane1)
                                .addGroup(metadataPanelLayout.createSequentialGroup().addComponent(addInputsLabel)
                                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                                        .addComponent(addSourcesTextField))
                                .addGroup(metadataPanelLayout.createSequentialGroup().addComponent(formatLabel)
                                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                                        .addComponent(contentTypeComboBox, 0, 759, Short.MAX_VALUE)))
                        .addContainerGap()));
        metadataPanelLayout.setVerticalGroup(metadataPanelLayout
                .createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                .addGroup(metadataPanelLayout.createSequentialGroup().addContainerGap().addGroup(metadataPanelLayout
                        .createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE).addComponent(formatLabel)
                        .addComponent(contentTypeComboBox, javax.swing.GroupLayout.PREFERRED_SIZE,
                                javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addGroup(metadataPanelLayout
                                .createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                                .addComponent(addSourcesTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 28,
                                        javax.swing.GroupLayout.PREFERRED_SIZE)
                                .addComponent(addInputsLabel))
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 81, Short.MAX_VALUE)
                        .addContainerGap()));

        addInputsLabel.getAccessibleContext().setAccessibleName(org.openide.util.NbBundle.getMessage(
                ResourceInfoPanel.class, "ResourceInfoPanel.addInputsLabel.AccessibleContext.accessibleName")); // NOI18N

        add(metadataPanel, java.awt.BorderLayout.CENTER);
        add(revisionFileWell, java.awt.BorderLayout.SOUTH);
    }// </editor-fold>//GEN-END:initComponents

    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JLabel addInputsLabel;
    private javax.swing.JTextField addSourcesTextField;
    private javax.swing.JComboBox contentTypeComboBox;
    private javax.swing.JLabel formatLabel;
    private javax.swing.JTextPane inputsTextPane;
    private javax.swing.JScrollPane jScrollPane1;
    private javax.swing.JPanel metadataPanel;
    private us.physion.ovation.ui.editor.FileWell revisionFileWell;
    // End of variables declaration//GEN-END:variables

    Logger logger = LoggerFactory.getLogger(ResourceInfoPanel.class);

    private final Map<JComponent, String> components = Maps.newHashMap();

    private void updateInputs() {
        final Multimap<String, Source> sources = HashMultimap.create();
        final Multimap<String, Revision> inputResources = HashMultimap.create();

        for (Measurement m : getMeasurements()) {
            for (String s : m.getSourceNames()) {
                sources.putAll(s, m.getEpoch().getInputSources().get(s));
            }
        }

        getResources().stream().forEach((r) -> {
            if (r.getContainingEntity() instanceof AnalysisRecord) {
                AnalysisRecord record = (AnalysisRecord) r.getContainingEntity();

                record.getInputs().keySet().stream().forEach((s) -> {
                    inputResources.put(s, ((AnalysisRecord) r.getContainingEntity()).getInputs().get(s));
                });
            }

            Revision rev = r.getHeadRevision();
            if (rev != null) {
                rev.getInputSources().forEach((s) -> {
                    sources.put(s.getLabel(), s);
                });
            }

        });

        getRevisions().stream().forEach((rev) -> {
            rev.getInputSources().forEach((s) -> {
                sources.put(s.getLabel(), s);
            });
        });

        EventQueueUtilities.runOnEDT(() -> {
            inputsTextPane.setText("");

            for (Map.Entry<String, Source> namedSource : sources.entries()) {
                insertInputsPanel(namedSource.getKey(), namedSource.getValue());

            }

            for (Map.Entry<String, Revision> namedInput : inputResources.entries()) {
                insertInputsPanel(namedInput.getKey(), namedInput.getValue());
            }
        });

    }

    private void insertInputsPanel(final String label, final OvationEntity entity) {
        JPanel sourcePanel = makeSourcePanel(label, entity);

        inputsTextPane.setCaretPosition(inputsTextPane.getDocument().getLength());
        inputsTextPane.insertComponent(sourcePanel);
    }

    private JPanel makeSourcePanel(final String label, final OvationEntity entity) {

        JPanel sourcePanel = new JPanel();
        sourcePanel.setBackground(getBackground());
        sourcePanel.setLayout(new FlowLayout(FlowLayout.LEFT, 3, 3));

        JButton sourceButton = new JButton(label);

        sourceButton.setOpaque(true);
        sourceButton.setBackground(Color.white);
        sourceButton.setForeground(EntityColors.getEntityColor(entity.getClass()));
        sourceButton.setBorder(null);

        sourceButton.addActionListener((final ActionEvent e) -> {
            //TODO find target, then decide where to show it
            if (entity instanceof Source) {
                RevealNode.forEntity(BrowserUtilities.SOURCE_BROWSER_ID, entity);
            } else {
                RevealNode.forEntity(BrowserUtilities.PROJECT_BROWSER_ID, entity);
            }
        });

        sourcePanel.add(sourceButton);
        JButton removeButton = new JButton("x");
        removeButton.setPreferredSize(new Dimension(15, 15));
        removeButton.addActionListener((ActionEvent e) -> {
            for (Measurement m : getMeasurements()) {
                Set<String> sourceNames = Sets.newHashSet(m.getSourceNames());

                sourceNames.remove(label);
                m.setSourceNames(sourceNames);

                try {
                    m.getEpoch().removeInputSource(label);
                } catch (IllegalArgumentException ex) {
                    // pass it's in use by another source
                }
            }

            for (Resource Resource : getResources()) {
                if (e instanceof Measurement) {
                    continue;
                }

                ((AnalysisRecord) Resource.getContainingEntity()).removeInput(label);

            }

            SwingUtilities.invokeLater(this::updateInputs);
        });

        sourcePanel.add(removeButton);
        sourcePanel.setBorder(new RoundedCornerBorder());
        sourcePanel.revalidate();
        sourcePanel.setMaximumSize(sourcePanel.getPreferredSize());

        sourcePanel.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));

        return sourcePanel;
    }

    class RoundedCornerBorder extends AbstractBorder {

        @Override
        public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
            Graphics2D g2 = (Graphics2D) g.create();
            g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            int r = height - 1;
            RoundRectangle2D round = new RoundRectangle2D.Float(x, y, width - 1, height - 1, r, r);
            Container parent = c.getParent();
            if (parent != null) {
                g2.setColor(parent.getBackground());
                Area corner = new Area(new Rectangle2D.Float(x, y, width, height));
                corner.subtract(new Area(round));
                g2.fill(corner);
            }
            g2.setColor(Color.GRAY);
            g2.draw(round);
            g2.dispose();
        }

        @Override
        public Insets getBorderInsets(Component c) {
            return new Insets(4, 8, 4, 8);
        }

        @Override
        public Insets getBorderInsets(Component c, Insets insets) {
            insets.left = insets.right = 8;
            insets.top = insets.bottom = 4;
            return insets;
        }
    }

    private void addSourceFromText(final DataContext ctx, String txt) throws OvationException {

        Iterable<String> sourceIds = Splitter.on(",").omitEmptyStrings().trimResults()
                .split(addSourcesTextField.getText());

        for (final String sourceId : sourceIds) {

            final ProgressHandle ph = ProgressHandleFactory.createHandle(Bundle.Adding_source(sourceId));

            EventQueueUtilities.runOffEDT(() -> {
                Set<Source> sources = Sets.newHashSet(ctx.getSourcesWithIdentifier(sourceId));
                if (sources.isEmpty()) {
                    sources = Sets.newHashSet(ctx.insertSource(sourceId, sourceId));
                    SwingUtilities.invokeLater(() -> {
                        BrowserUtilities.resetView(BrowserUtilities.SOURCE_BROWSER_ID);
                    });
                }

                for (Measurement m : getMeasurements()) {
                    Set<String> sourceNames = Sets.newHashSet(m.getSourceNames());
                    Epoch epoch = m.getEpoch();

                    for (Source s : sources) {
                        String epochId = s.getLabel() + " (" + s.getIdentifier() + ")"; //s.getURI().toString();
                        if (!s.equals(epoch.getInputSources().get(epochId))) {
                            epochId = s.getLabel() + " (" + s.getIdentifier() + "; " + s.getURI().toString() + ")";
                        }

                        if (!epoch.getInputSources().containsValue(s)) {
                            try {
                                epoch.addInputSource(epochId, s);
                            } catch (Throwable t) {
                                throw new OvationException("Unable to add input source", t);
                            }

                        }

                        sourceNames.add(epochId);
                    }

                    m.setSourceNames(sourceNames);
                }

                for (Resource r : getResources()) {
                    if ((r instanceof Measurement) || (r.getContainingEntity() instanceof AnalysisRecord)) {
                        continue;
                    }

                    Revision rev = r.getHeadRevision();
                    if (rev != null) {
                        sources.forEach((s) -> {
                            rev.addInputSource(s);
                        });
                    }
                }

                for (Revision rev : getRevisions()) {
                    if (rev.getResource().getContainingEntity() instanceof AnalysisRecord) {
                        continue;
                    }

                    sources.forEach((s) -> {
                        rev.addInputSource(s);
                    });
                }

                EventQueueUtilities.runOnEDT(() -> {
                    addSourcesTextField.setText("");
                    updateInputs();
                });
            }, ph);
        }
    }
}