com.wwidesigner.gui.StudyView.java Source code

Java tutorial

Introduction

Here is the source code for com.wwidesigner.gui.StudyView.java

Source

/**
 * Class to allow users to interact with instrument study models to analyze and optimize instruments.
 * 
 * Copyright (C) 2014, Edward Kort, Antoine Lefebvre, Burton Patkau.
 *
 * 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 com.wwidesigner.gui;

import java.awt.Dimension;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.prefs.Preferences;

import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.SwingUtilities;
import javax.swing.ToolTipManager;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
import javax.xml.bind.MarshalException;
import javax.xml.bind.UnmarshalException;

import org.apache.commons.math3.exception.ZeroException;
import org.xml.sax.SAXParseException;

import com.jidesoft.app.framework.DataModel;
import com.jidesoft.app.framework.DataModelException;
import com.jidesoft.app.framework.DataView;
import com.jidesoft.app.framework.event.EventSubscriber;
import com.jidesoft.app.framework.event.SubscriberEvent;
import com.jidesoft.app.framework.file.FileDataModel;
import com.jidesoft.app.framework.gui.DataViewPane;
import com.jidesoft.app.framework.gui.MessageDialogRequest;
import com.jidesoft.app.framework.gui.filebased.FileBasedApplication;
import com.jidesoft.app.framework.gui.framed.DockingApplicationFeature;
import com.jidesoft.docking.DockableFrame;
import com.jidesoft.tooltip.ExpandedTipUtils;
import com.jidesoft.tree.TreeUtils;
import com.wwidesigner.geometry.Instrument;
import com.wwidesigner.gui.util.DataOpenException;
import com.wwidesigner.gui.util.HoleNumberMismatchException;
import com.wwidesigner.gui.util.OptimizerMismatchException;
import com.wwidesigner.modelling.SketchInstrument;
import com.wwidesigner.note.Fingering;
import com.wwidesigner.optimization.Constraints;
import com.wwidesigner.util.Constants.LengthType;
import com.wwidesigner.util.InvalidFieldException;
import com.wwidesigner.util.OperationCancelledException;

/**
 * @author kort
 * 
 */
public class StudyView extends DataViewPane implements EventSubscriber {
    private static final long serialVersionUID = 1L;

    private JTree tree;
    private StudyModel study;

    @Override
    protected void initializeComponents() {
        // create file tree
        tree = new JTree() {
            @Override
            public String getToolTipText(MouseEvent e) {
                String tip = null;
                TreePath path = tree.getPathForLocation(e.getX(), e.getY());
                if (path != null) {
                    if (path.getPathCount() == 3) // it is a leaf
                    {
                        DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getLastPathComponent();
                        if (node instanceof TreeNodeWithToolTips) {
                            tip = ((TreeNodeWithToolTips) node).getToolTip();
                        }
                    }
                }
                return tip == null ? getToolTipText() : tip;
            }
        };
        // Show tooltips for the Study view, and let them persist for 8 seconds.
        ToolTipManager.sharedInstance().registerComponent(tree);
        ToolTipManager.sharedInstance().setDismissDelay(8000);
        // If a Study view node doesn't fit in the pane, expand it when hovering
        // over it.
        ExpandedTipUtils.install(tree);
        tree.setRootVisible(false);
        tree.getSelectionModel().setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION);
        tree.addMouseListener(new MouseAdapter() {
            public void mousePressed(MouseEvent e) {
                TreePath path = tree.getPathForLocation(e.getX(), e.getY());
                if (path != null) {
                    if (path.getPathCount() == 3) // it is a leaf
                    {
                        DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getLastPathComponent();
                        DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode) node.getParent();
                        String category = (String) parentNode.getUserObject();
                        study.setCategorySelection(category, (String) node.getUserObject());
                        if (StudyModel.INSTRUMENT_CATEGORY_ID.equals(category)
                                || StudyModel.TUNING_CATEGORY_ID.equals(category)) {
                            try {
                                study.validHoleCount();
                            } catch (Exception ex) {
                                showException(ex);
                            }
                        }
                    }
                    updateView();
                }
            }
        });
        JScrollPane scrollPane = new JScrollPane(tree);
        scrollPane.setPreferredSize(new Dimension(225, 100));
        add(scrollPane);

        Preferences myPreferences = getApplication().getPreferences();
        String modelName = myPreferences.get(OptimizationPreferences.STUDY_MODEL_OPT,
                OptimizationPreferences.NAF_STUDY_NAME);
        setStudyModel(modelName);
        study.setPreferences(myPreferences);

        getApplication().getEventManager().subscribe(WIDesigner.FILE_OPENED_EVENT_ID, this);
        getApplication().getEventManager().subscribe(WIDesigner.FILE_CLOSED_EVENT_ID, this);
        getApplication().getEventManager().subscribe(WIDesigner.FILE_SAVED_EVENT_ID, this);
        getApplication().getEventManager().subscribe(WIDesigner.WINDOW_RENAMED_EVENT_ID, this);
    }

    protected void updateView() {
        // Reset the Constraints category as needed.
        study.updateConstraints();

        // Build the selection tree.
        DefaultMutableTreeNode root = new DefaultMutableTreeNode();
        List<TreePath> selectionPaths = new ArrayList<TreePath>();

        // Add all static categories and selection options to the tree.
        for (String category : study.getCategoryNames()) {
            DefaultMutableTreeNode node = new DefaultMutableTreeNode(category);
            if (node != null) {
                node.setAllowsChildren(true);
                root.add(node);
            }
            String selectedSub = study.getSelectedSub(category);
            Map<String, String> toolTips = study.getCategory(category).getToolTips();
            for (String name : study.getSubcategories(category)) {
                TreeNodeWithToolTips childNode = new TreeNodeWithToolTips(name);
                if (childNode != null) {
                    node.add(childNode);
                    if (name.equals(selectedSub)) {
                        selectionPaths.add(new TreePath(childNode.getPath()));
                    }
                    String tip = toolTips.get(name);
                    if (tip != null) {
                        childNode.setToolTip(tip);
                    }
                }
            }
        }

        TreeModel model = new DefaultTreeModel(root);
        tree.setModel(model);
        TreeUtils.expandAll(tree);
        tree.setSelectionPaths(selectionPaths.toArray(new TreePath[0]));
        setStudyViewName();
        // Update on the appropriate thread
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                updateActions();
            }
        });
    }

    protected void setStudyViewName() {
        DockingApplicationFeature docking = (DockingApplicationFeature) getApplication()
                .getApplicationFeature(DockingApplicationFeature.class);
        DockableFrame frame = docking.getDockableFrame(this);
        frame.setTitle(study.getDisplayName());

    }

    protected void updateActions() {
        boolean isInstrumentSelected = false;
        boolean canDoTuning = false;
        boolean canDoOptimization = false;
        boolean canGraphNote = false;
        String selectedInstrumentName;
        selectedInstrumentName = study.getSelectedInstrumentName();
        isInstrumentSelected = (selectedInstrumentName != null && selectedInstrumentName != "");
        if (isInstrumentSelected) {
            canDoTuning = study.canTune();
            if (canDoTuning) {
                canDoOptimization = study.canOptimize();
            }
            canGraphNote = getSelectedFingering() != null;
        } else {
            // Event source cannot be null;
            selectedInstrumentName = "";
        }

        getApplication().getEventManager().publish(WIDesigner.OPTIMIZATION_ACTIVE_EVENT_ID, canDoOptimization);
        getApplication().getEventManager().publish(WIDesigner.TUNING_ACTIVE_EVENT_ID, canDoTuning);
        String constraintsDirectory = ((WIDesigner) getApplication()).getConstraintsRootDirectoryPath();
        getApplication().getEventManager().publish(WIDesigner.CONSTRAINTS_ACTIVE_EVENT_ID,
                study.isOptimizerFullySpecified(constraintsDirectory));
        getApplication().getEventManager().publish(WIDesigner.CONSTRAINTS_CAN_CREATE_EVENT_ID,
                study.isOptimizerCreateSpecified());
        getApplication().getEventManager().publish(WIDesigner.INSTRUMENT_SELECTED_EVENT_ID, selectedInstrumentName);
        getApplication().getEventManager().publish(WIDesigner.NOTE_SELECTED_EVENT_ID, canGraphNote);
    }

    @Override
    public void doEvent(SubscriberEvent event) {
        String eventId = event.getEvent();
        Object eventSource = event.getSource();
        if (eventSource instanceof FileDataModel) {
            FileDataModel source = (FileDataModel) eventSource;
            switch (eventId) {
            case WIDesigner.FILE_OPENED_EVENT_ID:
                try {
                    study.addDataModel(source, false);
                } catch (Exception ex) {
                    showException(ex);
                }
                break;
            case WIDesigner.FILE_CLOSED_EVENT_ID:
                study.removeDataModel(source);
                break;
            case WIDesigner.FILE_SAVED_EVENT_ID:
            case WIDesigner.WINDOW_RENAMED_EVENT_ID:
                try {
                    study.replaceDataModel(source);
                } catch (DataOpenException ex) {
                    showException(ex);
                }
                break;
            }
            updateView();
        }
    }

    public void getTuning() {
        try {
            study.calculateTuning("Tuning"); // This a title, not a constant
        } catch (Exception ex) {
            showException(ex);
        }
    }

    public void getSupplementaryInfo() {
        try {
            study.calculateSupplementaryInfo("Supplementary Information");
        } catch (Exception ex) {
            showException(ex);
        }
    }

    public void graphTuning() {
        try {
            study.graphTuning("Tuning"); // This a title, not a constant
        } catch (Exception ex) {
            showException(ex);
        }
    }

    public void getDefaultConstraints() {
        try {
            String xmlConstraints = study.getDefaultConstraints(
                    getApplication().getApplicationUIManager().getWindowsUI().getDialogParent());
            // For the cancelled hole-grouping scenario
            if (xmlConstraints != null) {
                addNewDataModel(xmlConstraints);
            }
        } catch (Exception ex) {
            showException(ex);
        }
    }

    public void getBlankConstraints() {
        try {
            String xmlConstraints = study.getBlankConstraints(
                    getApplication().getApplicationUIManager().getWindowsUI().getDialogParent());
            // For the cancelled hole-grouping scenario
            if (xmlConstraints != null) {
                addNewDataModel(xmlConstraints);
            }
        } catch (Exception ex) {
            showException(ex);
        }
    }

    public void optimizeInstrument() {
        try {
            String xmlInstrument = study.optimizeInstrument();
            addNewDataModel(xmlInstrument);
        } catch (Exception ex) {
            showException(ex);
        }
    }

    public void cancelOptimization() {
        study.cancelOptimization();
    }

    private void addNewDataModel(String xmlData) throws Exception {
        if (xmlData != null && !xmlData.isEmpty()) {
            FileBasedApplication app = (FileBasedApplication) getApplication();
            FileDataModel data = (FileDataModel) app.newData("xml");
            // At this point, we have a data model and data view that are still
            // empty. Load the data into the data model, then re-load the
            // data view from the data model.
            data.setData(xmlData);
            data.setDirty(true);
            // If a view has been assigned, update the study's data models.
            DataView view = app.getDataView(data);
            if (view != null) {
                view.updateView(data);
                study.addDataModel(data, true);
                updateView();
            }
        }
    }

    public void sketchInstrument() {
        try {
            SketchInstrument sketch = new SketchInstrument();
            sketch.draw(study.getInstrument(), study.getSelectedInstrumentName(), false);
        } catch (Exception ex) {
            showException(ex);
        }
    }

    public void compareInstrument() {
        try {
            FileBasedApplication app = (FileBasedApplication) getApplication();
            DataModel data = app.getFocusedModel();
            DataView view = app.getDataView(data);
            String xmlInstrument2 = ((XmlToggleView) view).getText();
            Instrument instrument2 = StudyModel.getInstrument(xmlInstrument2);
            LengthType defaultLengthType = ((WIDesigner) getApplication()).getApplicationLengthType();
            study.compareInstrument(data.getName(), instrument2, defaultLengthType);
        } catch (Exception e) {
            showException(e);
        }
    }

    public Fingering getSelectedFingering() {
        FileBasedApplication app = (FileBasedApplication) getApplication();
        if (app == null) {
            return null;
        }
        DataModel data = app.getFocusedModel();
        if (data == null) {
            return null;
        }
        DataView view = app.getDataView(data);
        if (view == null) {
            return null;
        }
        ContainedXmlView contained = ((XmlToggleView) view).getCurrentView();
        if (contained instanceof ContainedTuningView) {
            return ((ContainedTuningView) contained).getSelectedFingering();
        } else if (contained instanceof ContainedNafTuningView) {
            return ((ContainedNafTuningView) contained).getSelectedFingering();
        }
        return null;
    }

    public void graphNote() {
        try {
            Fingering fingering = getSelectedFingering();
            // Put in to show an understandable error.
            if (fingering == null) {
                throw new Exception(
                        "Cannot retrieve a selected fingering.  Select a fingering on the turning panel.");
            }
            study.graphNote(fingering);
        } catch (Exception e) {
            showException(e);
        }
    }

    public StudyModel getStudyModel() {
        return study;
    }

    /**
     * Set the study model given a study class name.
     */
    public void setStudyModel(StudyModel study) {
        this.study = study;

        DataModel[] models = getApplication().getDataModels();
        for (DataModel model : models) {
            if (model instanceof FileDataModel) {
                try {
                    study.addDataModel((FileDataModel) model, false);
                } catch (Exception ex) {
                    showException(ex);
                }
            }
        }
        updateView();
    }

    /**
     * Set the study model to a specified object.
     */
    public void setStudyModel(String studyClassName) {
        if (studyClassName.contentEquals(OptimizationPreferences.NAF_STUDY_NAME)) {
            setStudyModel(
                    new NafStudyModel(getApplication().getApplicationUIManager().getWindowsUI().getDialogParent()));
        } else if (studyClassName.contentEquals(OptimizationPreferences.WHISTLE_STUDY_NAME)) {
            setStudyModel(new WhistleStudyModel());
        } else if (studyClassName.contentEquals(OptimizationPreferences.FLUTE_STUDY_NAME)) {
            setStudyModel(new FluteStudyModel());
        } else if (studyClassName.contentEquals(OptimizationPreferences.REED_STUDY_NAME)) {
            setStudyModel(new ReedStudyModel());
        } else {
            // Default study model.
            setStudyModel(new WhistleStudyModel());
        }
    }

    /**
     * Display a message box reporting a processing exception.
     * 
     * @param ex
     *            - the exception encountered.
     */
    public void showException(Exception ex) {
        Exception exception = ex;
        Throwable cause = exception.getCause();
        if (exception instanceof DataModelException && cause instanceof Exception) {
            // We use DataModelExceptions as a wrapper for more specific cause
            // exception.
            exception = (Exception) cause;
            cause = exception.getCause();
        }
        int messageType = MessageDialogRequest.ERROR_STYLE; // Message box
        // style.
        final String exceptionType; // Message box title.
        String exceptionMessage = exception.getMessage(); // Message box text.
        boolean withTrace = false; // true to print on console log.

        if (exception instanceof DataOpenException) {
            DataOpenException doException = (DataOpenException) exception;
            exceptionType = doException.getType();
            messageType = doException.isWarning() ? MessageDialogRequest.WARNING_STYLE
                    : MessageDialogRequest.ERROR_STYLE;
        } else if (exception instanceof InvalidFieldException) {
            InvalidFieldException fldException = (InvalidFieldException) exception;
            exceptionType = fldException.getLabel();
            fldException.printMessages();
        } else if (exception instanceof HoleNumberMismatchException) {
            exceptionType = "Hole number mismatch";
            messageType = MessageDialogRequest.WARNING_STYLE;
        } else if (exception instanceof UnmarshalException || exception instanceof MarshalException) {
            exceptionType = "Invalid XML Definition";
            exceptionMessage = "Invalid XML structure.\n";
            exceptionMessage += exception.getCause().getMessage();
            if (cause instanceof SAXParseException) {
                SAXParseException parseEx = (SAXParseException) cause;
                exceptionMessage += "\nAt line " + parseEx.getLineNumber() + ", column " + parseEx.getColumnNumber()
                        + ".";
            }
        } else if (exception instanceof DataModelException) {
            withTrace = true;
            exceptionType = "Error in data model";
        } else if (exception instanceof ZeroException) {
            exceptionType = "Operation cannot be performed";
            exceptionMessage = "Optimization requires at least 1 variable.";
            messageType = MessageDialogRequest.ERROR_STYLE;
        } else if (exception instanceof OperationCancelledException) {
            exceptionType = "Operation cancelled";
            messageType = MessageDialogRequest.WARNING_STYLE;
        } else if (exception instanceof OptimizerMismatchException) {
            exceptionType = "Operation cancelled";
            messageType = MessageDialogRequest.ERROR_STYLE;
        } else {
            withTrace = true;
            exceptionType = "Operation failed";
        }

        // showException can be called on any thread, but message dialog must
        // run on the AWT thread.
        final String dialogMessage = exceptionMessage;
        final int dialogType = messageType;
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                MessageDialogRequest.showMessageDialog(getApplication(), dialogMessage, exceptionType, dialogType);
            }
        });
        if (withTrace) {
            System.out.println(exception.getClass().getName() + " Exception: " + exceptionMessage);
            exception.printStackTrace();
        }
    }

    public File getConstraintsLeafDirectory(String rootDirectoryPath) {
        return study.getConstraintsLeafDirectory(rootDirectoryPath);
    }

    public File getConstraintsLeafDirectory(String rootDirectoryPath, Constraints constraints) {
        return study.getConstraintsLeafDirectory(rootDirectoryPath, constraints);
    }

    class TreeNodeWithToolTips extends DefaultMutableTreeNode {
        private String toolTip;
        private String displayName;

        public TreeNodeWithToolTips(Object userObject) {
            super(userObject);

            if (userObject != null) {
                String key = (String) userObject;
                if (key.contains(File.separator)) {
                    displayName = key.substring(key.lastIndexOf(File.separator) + 1);
                    setToolTip(key);
                } else {
                    displayName = key;
                }
            } else {
                displayName = new String();
            }
        }

        public void setToolTip(String tip) {
            toolTip = tip;
        }

        public String getToolTip() {
            return toolTip;
        }

        @Override
        public String toString() {
            return displayName;
        }
    }
}