ModelElementsPanel.java

// @formatter:off
 /*******************************************************************************
 *
 * This file is part of JMad.
 * 
 * Copyright (c) 2008-2011, CERN. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 ******************************************************************************/
// @formatter:on

package cern.accsoft.steering.jmad.gui.panels;

import javax.swing.*;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.AbstractTableModel;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.util.ArrayList;
import java.util.List;

import cern.accsoft.steering.jmad.domain.elem.Element;
import cern.accsoft.steering.jmad.domain.ex.JMadModelException;
import cern.accsoft.steering.jmad.domain.machine.Range;
import cern.accsoft.steering.jmad.domain.misalign.MisalignmentConfiguration;
import cern.accsoft.steering.jmad.domain.optics.EditableOpticPointImpl;
import cern.accsoft.steering.jmad.domain.optics.OpticPoint;
import cern.accsoft.steering.jmad.gui.components.DoubleTableCellRenderer;
import cern.accsoft.steering.jmad.gui.manage.ElementSelectionManager;
import cern.accsoft.steering.jmad.gui.mark.MarkedElementsManager;
import cern.accsoft.steering.jmad.gui.mark.MarkedElementsManagerListener;
import cern.accsoft.steering.jmad.model.JMadModel;
import cern.accsoft.steering.jmad.model.JMadModelListener;
import cern.accsoft.steering.jmad.model.manage.JMadModelManager;
import cern.accsoft.steering.jmad.model.manage.JMadModelManagerAdapter;
import cern.accsoft.steering.util.gui.CompUtils;
import cern.accsoft.steering.util.gui.menu.ActionProvider;
import cern.accsoft.steering.util.gui.menu.MousePopupListener;
import cern.accsoft.steering.util.gui.menu.TablePopupMenu;
import cern.accsoft.steering.util.gui.panels.TableFilterPanel;
import cern.accsoft.steering.util.gui.table.BeanTableModel;
import cern.accsoft.steering.util.gui.table.SelectionSetTableModel;
import cern.accsoft.steering.util.gui.table.TableModelSelectionAdapter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * this class represents a panel, which enables the display of all elements of a model including the details of the
 * element. It also offers the possibility to select certain properties of some elements (when an Edit Handler is
 * plugged in).
 * 
 * @author Kajetan Fuchsberger (kajetan.fuchsberger at cern.ch)
 */
public class ModelElementsPanel extends JPanel implements EditHandlerUser<ModelElementsPanelEditHandler> {
    private static final long serialVersionUID = 1L;

    /** the logger for the class */
    private final static Logger logger = LoggerFactory.getLogger(ModelElementsPanel.class);

    /**
     * The initial location of the divider between elements-table and element-table.
     */
    private final static int DIVIDER_LOCATION = 250;

    /**
     * here we can plugin an edithandler to process selection/deselection of special attributes.
     */
    private ModelElementsPanelEditHandler editHandler;

    /** The model manager that e.g. determines if the model changes */
    private JMadModelManager modelManager;

    /**
     * The manager, which keeps track of all elements, that shall be displayed as markers in the charts
     */
    private MarkedElementsManager markedElementsManager;

    /**
     * the manager which keeps track of all selected elements
     */
    private ElementSelectionManager elementSelectionManager;

    /** the table models for the elements-table and for the element-table. */
    private ModelElementsTableModel tableModelElements = null;
    private ModelElementTableModel tableModelElement = null;
    private BeanTableModel tableModelElementOptic = null;

    /** the text field which displays the name of the actual selected element. */
    private JTextField txtElementName = null;

    /** the listener, which shall be added to every model */
    private JMadModelListener modelListener = new JMadModelListener() {

        @Override
        public void elementsChanged() {
            tableModelElement.fireTableDataChanged();
            validate();
        }

        @Override
        public void opticsChanged() {
            tableModelElementOptic.fireTableDataChanged();
            validate();
        }

        @Override
        public void rangeChanged(Range newRange) {
            tableModelElements.fireTableDataChanged();
            validate();
        }

        @Override
        public void becameDirty() {
            /* for the moment do not update every time a single value changes. */
        }

        @Override
        public void opticsDefinitionChanged() {
            /* for the moment do not update every time a single value changes. */
        }
    };

    /**
     * the constructor
     */
    public ModelElementsPanel() {
        super(new BorderLayout());
        initComponents();
    }

    /**
     * set the model-manager which determines e.g. when the model changes.
     * 
     * @param modelManager
     */
    public final void setModelManager(JMadModelManager modelManager) {
        this.modelManager = modelManager;

        if (this.modelManager.getActiveModel() != null) {
            this.modelManager.getActiveModel().addListener(modelListener);
        }

        modelManager.addListener(new JMadModelManagerAdapter() {

            @Override
            public void changedActiveModel(JMadModel newModel) {
                if (newModel != null) {
                    newModel.addListener(modelListener);
                }
                tableModelElements.fireTableDataChanged();
            }

        });
    }

    /**
     * create all containing Components
     */
    private final void initComponents() {
        JTable table;

        /*
         * the panel for the overall list
         */
        JPanel listPanel = new JPanel(new GridBagLayout());
        GridBagConstraints constraints = new GridBagConstraints();
        constraints.gridx = 0;
        constraints.gridy = 0;
        constraints.weightx = 1;
        constraints.weighty = 0;
        constraints.fill = GridBagConstraints.BOTH;

        tableModelElements = new ModelElementsTableModel();
        table = new JTable(tableModelElements);
        table.setDefaultRenderer(Double.class, new DoubleTableCellRenderer());
        table.setAutoCreateRowSorter(true);
        table.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
        table.getSelectionModel().addListSelectionListener(new ElementSelectionAdapter(table));
        tableModelElements.setTableModelSelectionAdapter(new TableModelSelectionAdapter(table));
        JScrollPane elementsScrollPane = CompUtils.createScrollPane(table);

        /* add a popup-menu to the table */
        new MousePopupListener(table, new TablePopupMenu(new MisalignmentCreator()));

        /*
         * the filter for the elements-table
         */
        listPanel.add(new TableFilterPanel(table), constraints);

        constraints.weighty = 1;
        constraints.gridx = 0;
        constraints.gridy++;
        constraints.gridwidth = 2;
        listPanel.add(elementsScrollPane, constraints);

        /*
         * the panel for the details
         */
        JPanel detailPanel = new JPanel(new GridBagLayout());
        constraints.gridx = 0;
        constraints.gridy = 0;
        constraints.weightx = 1;
        constraints.weighty = 0;
        constraints.fill = GridBagConstraints.BOTH;

        txtElementName = new JTextField();
        txtElementName.setEditable(false);
        detailPanel.add(txtElementName, constraints);

        constraints.weighty = 1;
        constraints.gridy++;
        tableModelElement = new ModelElementTableModel();
        table = new JTable(tableModelElement);
        table.setDefaultRenderer(Double.class, new DoubleTableCellRenderer());
        detailPanel.add(CompUtils.createScrollPane(table), constraints);

        constraints.gridy++;
        tableModelElementOptic = new BeanTableModel(OpticPoint.class);
        table = new JTable(tableModelElementOptic);
        table.setDefaultRenderer(Double.class, new DoubleTableCellRenderer());
        detailPanel.add(CompUtils.createScrollPane(table), constraints);

        JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);

        splitPane.setLeftComponent(listPanel);
        // splitPane.setLeftComponent(elementsScrollPane);
        splitPane.setRightComponent(detailPanel);
        add(splitPane, BorderLayout.CENTER);
        splitPane.setDividerLocation(DIVIDER_LOCATION);
    }

    /**
     * @return the actually selected elements
     */
    private ArrayList<Element> getSelectedElements() {
        ArrayList<Element> elements = new ArrayList<Element>();

        if (modelManager != null) {
            Range range = modelManager.getActiveModel().getActiveRange();
            if (range != null) {
                for (Integer index : this.tableModelElements.getTableModelSelectionAdapter().getSelectedRowIndizes()) {
                    elements.add(range.getElements().get(index));
                }
            }
        }
        return elements;
    }

    private Element getElement(int modelIndex) {
        if ((modelIndex >= 0) && (modelManager != null)) {
            Range range = modelManager.getActiveModel().getActiveRange();
            if (range != null) {
                return range.getElements().get(modelIndex);
            }
        }
        return null;
    }

    /**
     * this class is attended to be plugged in into the table-menu
     * 
     * @author Kajetan Fuchsberger (kajetan.fuchsberger at cern.ch)
     */
    private class MisalignmentCreator implements ActionProvider {

        /**
         * this action adds an empty misalignment to the model, for each selected element.
         */
        private Action createMisalignmentsAction = new AbstractAction("create misalignments") {
            private static final long serialVersionUID = -7332873258819061907L;

            @Override
            public void actionPerformed(ActionEvent evt) {
                List<Element> elements = getSelectedElements();
                for (Element element : elements) {
                    Range range = modelManager.getActiveModel().getActiveRange();
                    range.addMisalignment(new MisalignmentConfiguration(element.getName()));
                }
            }

        };

        @Override
        public List<Action> getActions() {
            List<Action> actions = new ArrayList<Action>();
            actions.add(this.createMisalignmentsAction);
            return actions;
        }

    }

    /**
     * This class is the implementation of a listener to change the selected elements in the elements-table.
     * 
     * @author Kajetan Fuchsberger (kajetan.fuchsberger at cern.ch)
     */
    private class ElementSelectionAdapter implements ListSelectionListener {
        private JTable table = null;

        public ElementSelectionAdapter(JTable table) {
            this.table = table;
        }

        @Override
        public void valueChanged(ListSelectionEvent event) {
            if (event.getSource() == table.getSelectionModel()) {
                int index = table.getSelectedRow();
                Element element = null;
                if (index >= 0) {
                    element = getElement(table.convertRowIndexToModel(index));
                }
                displayElementDetails(element);
                if (getElementSelectionManager() != null) {
                    getElementSelectionManager().setSelectedElements(getSelectedElements(), element);
                }
            }
        }

    }

    /**
     * This class is the table model for the table of available elements.
     * 
     * @author Kajetan Fuchsberger (kajetan.fuchsberger at cern.ch)
     */
    private class ModelElementsTableModel extends SelectionSetTableModel {
        private static final long serialVersionUID = 1L;

        private final static int COLUMN_COUNT = 4;

        private final static int COL_NUMBER = 0;
        private final static int COL_MARK = 1;
        private final static int COL_NAME = 2;
        private final static int COL_TYPE = 3;

        @Override
        public int getColumnCount() {
            return COLUMN_COUNT;
        }

        @Override
        public int getRowCount() {
            if ((modelManager == null) || (modelManager.getActiveModel() == null)
                    || (modelManager.getActiveModel().getActiveRange() == null)) {
                return 0;
            }
            return getElements().size();
        }

        @Override
        public boolean isCellEditable(int row, int col) {
            if (col == COL_MARK) {
                return true;
            } else {
                return false;
            }
        }

        @Override
        public void setValueAt(Object value, int row, int col) {
            if (modelManager == null) {
                return;
            }

            if (col == COL_MARK) {
                if (getMarkedElementsManager() == null) {
                    return;
                }
                Element element = modelManager.getActiveModel().getActiveRange().getElements().get(row);
                if ((Boolean) value) {
                    getMarkedElementsManager().addElementName(element.getName());
                } else {
                    getMarkedElementsManager().removeElementName(element.getName());
                }
            }
        }

        @Override
        public Object getValueAt(int row, int col) {
            if (modelManager == null) {
                return null;
            }

            Element element = modelManager.getActiveModel().getActiveRange().getElements().get(row);
            switch (col) {
            case COL_NUMBER:
                return row;
            case COL_MARK:
                if (getMarkedElementsManager() == null) {
                    return false;
                } else {
                    return getMarkedElementsManager().contains(element.getName());
                }
            case COL_NAME:
                return element.getName();
            case COL_TYPE:
                return element.getMadxElementType().toString();
            default:
                return null;
            }
        }

        @Override
        public Class<?> getColumnClass(int col) {
            switch (col) {
            case COL_NUMBER:
                return Integer.class;
            case COL_MARK:
                return Boolean.class;
            case COL_NAME:
                return String.class;
            case COL_TYPE:
                return String.class;
            default:
                return null;
            }
        }

        @Override
        public String getColumnName(int col) {
            switch (col) {
            case COL_NUMBER:
                return "number";
            case COL_MARK:
                return "mark";
            case COL_NAME:
                return "name";
            case COL_TYPE:
                return "type";
            default:
                return null;
            }
        }

        private List<Element> getElements() {
            if ((modelManager != null) && (modelManager.getActiveModel() != null)
                    && (modelManager.getActiveModel().getActiveRange() != null)) {
                return modelManager.getActiveModel().getActiveRange().getElements();
            }
            return new ArrayList<Element>();
        }
    }

    /**
     * This is the table model for the table of details of one element
     */
    private class ModelElementTableModel extends AbstractTableModel {

        private static final long serialVersionUID = 1L;

        /* the name an position are not accessible through property - name */
        private final static int DEFAULT_ATTRIBUTE_COUNT = 1;

        private final static int ROW_POSITION = 0;
        private final static String NAME_POSITIONE = "position";

        /* the columns */
        private final static int COLUMN_COUNT = 3;

        private final static int COL_SELECTED = 0;
        private final static int COL_ATTR_NAME = 1;
        private final static int COL_ATTR_VALUE = 2;

        private Element element = null;

        public void setElement(Element element) {
            this.element = element;
            fireTableDataChanged();
        }

        @Override
        public int getColumnCount() {
            return COLUMN_COUNT;
        }

        @Override
        public int getRowCount() {
            if (element != null) {
                return element.getAttributeNames().size() + DEFAULT_ATTRIBUTE_COUNT;
            } else {
                return 0;
            }
        }

        @Override
        public boolean isCellEditable(int row, int col) {
            if ((row != ROW_POSITION) && (col != COL_ATTR_NAME)) {
                return (getValueAt(row, COL_ATTR_VALUE) != null);
            } else {
                return false;
            }
        }

        @Override
        public Object getValueAt(int row, int col) {
            if (element == null) {
                return null;
            }

            switch (col) {
            case COL_ATTR_NAME:
                if (row == ROW_POSITION) {
                    return NAME_POSITIONE;
                } else {
                    return element.getAttributeNames().get(row - DEFAULT_ATTRIBUTE_COUNT);
                }
            case COL_ATTR_VALUE:
                if (row == ROW_POSITION) {
                    return element.getPosition().getValue();
                } else {
                    return element.getAttribute(element.getAttributeNames().get(row - DEFAULT_ATTRIBUTE_COUNT));
                }
            case COL_SELECTED:
                if (row == ROW_POSITION) {
                    return false;
                } else {
                    if (editHandler != null) {
                        return editHandler.getSelectionValue(element,
                                element.getAttributeNames().get(row - DEFAULT_ATTRIBUTE_COUNT));
                    } else {
                        return false;
                    }
                }
            default:
                return null;
            }
        }

        @Override
        public void setValueAt(Object value, int row, int col) {
            if (row < DEFAULT_ATTRIBUTE_COUNT) {
                return;
            }

            String attributeName = element.getAttributeNames().get(row - DEFAULT_ATTRIBUTE_COUNT);
            switch (col) {
            case COL_SELECTED:
                if (editHandler != null) {
                    ArrayList<Element> elements = getSelectedElements();
                    for (Element elem : elements) {
                        editHandler.setSelectionValue(elem, attributeName, (Boolean) value);
                    }
                    fireTableCellUpdated(row, col);
                }
                break;
            case COL_ATTR_VALUE:
                ArrayList<Element> elements = getSelectedElements();
                for (Element elem : elements) {
                    elem.setAttribute(attributeName, (Double) value);
                }
                fireTableCellUpdated(row, col);
                break;
            }
        }

        @Override
        public Class<?> getColumnClass(int col) {
            switch (col) {
            case COL_ATTR_NAME:
                return String.class;
            case COL_ATTR_VALUE:
                return Double.class;
            case COL_SELECTED:
                return Boolean.class;
            default:
                return null;
            }
        }

        @Override
        public String getColumnName(int col) {
            switch (col) {
            case COL_ATTR_NAME:
                return "attribute name";
            case COL_ATTR_VALUE:
                return "value";
            case COL_SELECTED:
                if (editHandler != null) {
                    return editHandler.getCheckableColumnHeader();
                } else {
                    return "selected";
                }
            default:
                return null;
            }
        }
    }

    /**
     * displays the data of the given element.
     * 
     * @param element the element whose data to display
     */
    private void displayElementDetails(Element element) {
        if (element != null) {
            txtElementName.setText(element.getName());
            tableModelElement.setElement(element);
            tableModelElementOptic.setBean(getElementOptics(element));
        } else {
            txtElementName.setText("");
            tableModelElement.setElement(null);
            tableModelElementOptic.setBean(null);
        }
    }

    /**
     * retrieves the optics-point for an element from the model and handles exceptions if they occur.
     * 
     * @param element the element for which to get the optics point
     * @return the {@link EditableOpticPointImpl}
     */
    private OpticPoint getElementOptics(Element element) {
        JMadModel model = modelManager.getActiveModel();
        try {
            return model.getOptics().getPoint(element);
        } catch (JMadModelException e) {
            logger.warn("no optics-information found for element '" + element.getName() + "'", e);
            return null;
        }
    }

    /**
     * @return the editHandler
     */
    public final ModelElementsPanelEditHandler getEditHandler() {
        return editHandler;
    }

    /**
     * @param editHandler the editHandler to set
     */
    @Override
    public final void setEditHandler(TablePanelEditHandler editHandler) {
        if (editHandler instanceof ModelElementsPanelEditHandler) {
            this.editHandler = (ModelElementsPanelEditHandler) editHandler;
        }
    }

    /**
     * @param markedElementsManager the markedElementsManager to set
     */
    public void setMarkedElementsManager(MarkedElementsManager markedElementsManager) {
        this.markedElementsManager = markedElementsManager;
        markedElementsManager.addListener(new MarkedElementsManagerListener() {

            @Override
            public void addedElementName(String elementName) {
                tableModelElements.fireTableDataChanged();
            }

            @Override
            public void removedElementName(String elementName) {
                tableModelElements.fireTableDataChanged();
            }
        });
    }

    /**
     * @return the markedElementsManager
     */
    private MarkedElementsManager getMarkedElementsManager() {
        return markedElementsManager;
    }

    public void setElementSelectionManager(ElementSelectionManager elementSelectionManager) {
        this.elementSelectionManager = elementSelectionManager;
    }

    private ElementSelectionManager getElementSelectionManager() {
        return elementSelectionManager;
    }

}