SiLeBAT/FSK-Lab

View on GitHub
de.bund.bfr.knime.pmm.nodes/src/de/bund/bfr/knime/pmm/manualmodelconf/ui/ModelTableModel.java

Summary

Maintainability
F
1 wk
Test Coverage
/*******************************************************************************
 * Copyright (c) 2015 Federal Institute for Risk Assessment (BfR), Germany
 *
 * 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/>.
 *
 * Contributors:
 *     Department Biological Safety - BfR
 *******************************************************************************/
package de.bund.bfr.knime.pmm.manualmodelconf.ui;

import java.awt.Color;
import java.awt.Component;
import java.awt.Font;
import java.awt.event.KeyEvent;
import java.util.HashMap;
import java.util.SortedMap;

import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JRadioButton;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.ListSelectionModel;
import javax.swing.SwingConstants;
import javax.swing.UIDefaults;
import javax.swing.UIManager;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;

import de.bund.bfr.knime.pmm.common.DepXml;
import de.bund.bfr.knime.pmm.common.ParamXml;
import de.bund.bfr.knime.pmm.common.ParametricModel;
import de.bund.bfr.knime.pmm.common.PmmXmlElementConvertable;
import de.bund.bfr.knime.pmm.common.ui.DoubleTextField;

public class ModelTableModel extends JTable {

    private static final long serialVersionUID = -6782674430592418376L;
        
    // If changing here: please look at: getValueAt(), setValueAt(), isCellEditable(), getColumnClass(), MyTableCellRenderer
    private String[] columns = new String[]{"Parameter", "Unit", "Independent", "Value", "StandardErr", "Min", "Max", "Description"};
    private HashMap<String, ParametricModel> m_secondaryModels = null;
    private JRadioButton radioButton3 = null;
    private ParametricModel thePM;
    private boolean hasChanged = false;
    private boolean repaintAgain = false;
    private HashMap<String, Boolean> rowHasChanged;
    
    private boolean isBlankEditor = false;

    public ModelTableModel() {
        super();
        BooleanTableModel btm = new BooleanTableModel();
        rowHasChanged = new HashMap<>();
        this.setModel(btm);    
        this.setDefaultRenderer(Object.class, new MyTableCellRenderer());
        this.setDefaultRenderer(Boolean.class, new MyTableCellRenderer());
        this.setDefaultRenderer(Double.class, new MyTableCellRenderer());
        this.getTableHeader().setReorderingAllowed(false);
        this.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        this.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);
    }
    public void setPM(ParametricModel pm, HashMap<String, ParametricModel> secondaryModels, JRadioButton radioButton3) {
        thePM = pm;
        m_secondaryModels = secondaryModels;
        this.radioButton3 = radioButton3;
        thePM.validateParams();
        this.revalidate();
        hasChanged = false;
        rowHasChanged = new HashMap<>();
    }
    public ParametricModel getPM() {
        return thePM;
    }
    public boolean hasChanged() {
        return hasChanged;
    }
    public boolean isEstimated() {
        if (thePM.getRsquared() != null && !Double.isNaN(thePM.getRsquared()) ||
                thePM.getRms() != null && !Double.isNaN(thePM.getRms()) ||
                thePM.getAic() != null && !Double.isNaN(thePM.getAic()) ||
                thePM.getBic() != null && !Double.isNaN(thePM.getBic()))
            return true;
        for (PmmXmlElementConvertable el : thePM.getParameter().getElementSet()) {
            if (el instanceof ParamXml) {
                ParamXml px = (ParamXml) el;
                if (px.error != null && !Double.isNaN(px.error) ||
                        px.value != null && !Double.isNaN(px.value)) {
                    return true;
                }
            }
        }

        return false;
    }
    public void clearTable() {
        thePM = new ParametricModel("", "", null, 1);
        this.revalidate();
        hasChanged = false;
        rowHasChanged = new HashMap<>();
    }

    // Here: functionality: always overwrite cell except for pressed F2, which means: activate cell
    @Override
    public Component prepareEditor(final TableCellEditor editor, final int row, final int column) {
        Component c = super.prepareEditor(editor, row, column);        
        if (isBlankEditor) {
            ((JTextField) c).setText("");
        }        
        return c;
    }
    @Override
    protected boolean processKeyBinding(final KeyStroke ks, final KeyEvent e, final int condition, final boolean pressed) {
        char ch = e.getKeyChar();
        if (ch == ',') {
              e.setKeyChar('.');
        }
        if (!KeyEvent.getKeyText(e.getKeyCode()).equals("F2")) {
            isBlankEditor = true;
        }        
        boolean retValue = super.processKeyBinding(ks, e, condition, pressed);        
        isBlankEditor = false;
        return retValue;
    }
    
    private class BooleanTableModel extends AbstractTableModel {
 
        /**
         * 
         */
        private static final long serialVersionUID = 1L;

        public int getRowCount() {
            if (thePM == null) return 0;
            else return thePM.getAllParVars().size() + 1;
        }
 
        public int getColumnCount() {
            return columns.length;
        }
 
        public Object getValueAt(int rowIndex, int columnIndex) {
            if (thePM == null) return null;
            if (rowIndex == 0) {
                DepXml dx = thePM.getDepXml();
                if (columnIndex < 2) return thePM.getDepVar();
                else if (columnIndex == 5 && dx != null) return dx.min;
                else if (columnIndex == 6 && dx != null) return dx.max;
                else if (columnIndex > 6) return thePM.getDepDescription();
                else return null;
            }
            SortedMap<String, Boolean> sm = thePM.getAllParVars();
            Object[] oa = sm.keySet().toArray();
            if (rowIndex > 0 && rowIndex <= oa.length) {
                String rowID = oa[rowIndex - 1].toString();
                boolean isIndep = sm.get(rowID);
                if (columnIndex == 0) return rowID;
                if (columnIndex == 1) return rowID;
                if (columnIndex == 2) return isIndep;
                if (columnIndex == 3) return isIndep ? null : 
                    thePM.getParamValue(rowID) == null || Double.isNaN(thePM.getParamValue(rowID)) ? null : thePM.getParamValue(rowID);
                if (columnIndex == 4) return isIndep ? null : 
                    thePM.getParamError(rowID) == null || Double.isNaN(thePM.getParamError(rowID)) ? null : thePM.getParamError(rowID);
                if (columnIndex == 5) return isIndep ? (thePM.getIndepMin(rowID) == null || Double.isNaN(thePM.getIndepMin(rowID)) ? null : thePM.getIndepMin(rowID)) :
                    (thePM.getParamMin(rowID) == null || Double.isNaN(thePM.getParamMin(rowID)) ? null : thePM.getParamMin(rowID));
                if (columnIndex == 6) return isIndep ? (thePM.getIndepMax(rowID) == null || Double.isNaN(thePM.getIndepMax(rowID)) ? null : thePM.getIndepMax(rowID)) :
                    (thePM.getParamMax(rowID) == null || Double.isNaN(thePM.getParamMax(rowID)) ? null : thePM.getParamMax(rowID));
                if (columnIndex == 7) return isIndep ? (thePM.getIndepDescription(rowID) == null ? null : thePM.getIndepDescription(rowID)) :
                    (thePM.getParamDescription(rowID) == null ? null : thePM.getParamDescription(rowID));
            }
            return null;
        }
 
        public void setValueAt(Object o, int rowIndex, int columnIndex) {
            if (thePM == null) return;
            if (rowIndex == 0) {
                DepXml dx = thePM.getDepXml();
                if (columnIndex == 1) {
                    rowHasChanged.put(thePM.getDepVar(), true);
                    hasChanged = true;
                    repaintAgain = true;
                }
                else if (columnIndex == 5 && dx != null && (o == null || o instanceof Double)) {
                    dx.min = (Double) o;
                    rowHasChanged.put(thePM.getDepVar(), true);
                    hasChanged = true;
                    repaintAgain = true;
                }
                else if (columnIndex == 6 && dx != null && (o == null || o instanceof Double)) {
                    dx.max = (Double) o;
                    rowHasChanged.put(thePM.getDepVar(), true);
                    hasChanged = true;
                    repaintAgain = true;
                }
                else if (columnIndex == 7) {
                    thePM.setDepDescription(o == null ? "" : o.toString());
                    rowHasChanged.put(thePM.getDepVar(), true);
                    hasChanged = true;
                    repaintAgain = true;
                }
                return;
            }
            SortedMap<String, Boolean> sm = thePM.getAllParVars();
            Object[] oa = sm.keySet().toArray();
            if (rowIndex <= oa.length) {
                String paramName = oa[rowIndex - 1].toString();
                if (columnIndex == 2 && o instanceof Boolean) {
                    boolean isIndep = (Boolean) o;
                    if (isIndep) {
                        thePM.addIndepVar(paramName, thePM.getParamMin(paramName), thePM.getParamMax(paramName), thePM.getParamCategory(paramName), thePM.getParamUnit(paramName), thePM.getParamDescription(paramName));
                        thePM.removeParam(paramName);
                        if (thePM.getLevel() == 1) { // only one indepVar allowed
                            for (int i=1;i<this.getRowCount();i++) {
                                if (i != rowIndex && this.getValueAt(i, 2) != null && (Boolean) this.getValueAt(i, 2)) {
                                    this.setValueAt(false, i, 2);
                                }
                            }
                        }
                    }
                    else {
                        thePM.addParam(paramName, null, Double.NaN, Double.NaN, thePM.getIndepMin(paramName), thePM.getIndepMax(paramName), thePM.getIndepCategory(paramName), thePM.getIndepUnit(paramName), thePM.getIndepDescription(paramName));
                        thePM.removeIndepVar(paramName);
                    }
                    hasChanged = true;
                    repaintAgain = true;
                }
                else {
                    boolean isIndep = sm.get(paramName);
                    if (isIndep) {
                        if (columnIndex == 5 && (o == null || o instanceof Double)) thePM.setIndepMin(paramName, (Double) o);
                        if (columnIndex == 6 && (o == null || o instanceof Double)) thePM.setIndepMax(paramName, (Double) o);
                        if (columnIndex == 7) {
                            thePM.setIndepDescription(paramName, o == null ? "" : o.toString());
                            hasChanged = true;
                            repaintAgain = true;
                        }
                    }
                    else {
                        if (columnIndex == 3 && (o == null || o instanceof Double)) thePM.setParamValue(paramName, (Double) o);
                        if (columnIndex == 4 && (o == null || o instanceof Double)) thePM.setParamError(paramName, (Double) o);
                        if (columnIndex == 5 && (o == null || o instanceof Double)) {
                            thePM.setParamMin(paramName, (Double) o);
                            hasChanged = true;
                            repaintAgain = true;
                        }
                        if (columnIndex == 6 && (o == null || o instanceof Double)) {
                            thePM.setParamMax(paramName, (Double) o);
                            hasChanged = true;
                            repaintAgain = true;
                        }
                        if (columnIndex == 7) {
                            thePM.setParamDescription(paramName, o == null ? "" : o.toString());
                            hasChanged = true;
                            repaintAgain = true;
                        }
                    }
                }
                //super.fireTableCellUpdated(rowIndex, columnIndex);
                rowHasChanged.put(paramName, true);
            }
        }
 
        public String getColumnName(int columnIndex) {
            return columns[columnIndex];
        }
 
        public boolean isCellEditable(final int rowIndex, final int columnIndex) {
            if (rowIndex == 0) return (columnIndex >= 5);
            Boolean indep = (Boolean) this.getValueAt(rowIndex, 2);
            if (indep == null) indep = false;
            return columnIndex == 2 || columnIndex > 4 || (!indep && (columnIndex == 4 || columnIndex == 3));
        }

        public Class<?> getColumnClass(int columnIndex) {
            if (columnIndex == 0) return Object.class;
            else if (columnIndex == 1) return Object.class;
            else if (columnIndex == 2) return Boolean.class;
            else if (columnIndex == 3) return Double.class;
            else if (columnIndex == 4) return Double.class;
            else if (columnIndex == 5) return Double.class;
            else if (columnIndex == 6) return Double.class;
            else if (columnIndex == 7) return String.class;
            else return Object.class;
        }
    }

    private class MyTableCellRenderer implements TableCellRenderer {
          public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int rowIndex, int columnIndex) {
              JComponent c;
              if (columnIndex == 0) {
                    Boolean indep = (Boolean) table.getValueAt(rowIndex, 2);
                    if (indep == null) indep = false;
                    String text = "";
                    if (value != null) {
                        text = value.toString();
                        if (rowHasChanged.get(value) != null && rowHasChanged.get(value)) text += "*";
                    }
                    JComponent editor;
                    if (thePM.getLevel() == 1 && !indep && rowIndex > 0 && radioButton3.isSelected()) {
                        editor = new JButton(text);                      
                    }
                    else {
                        editor = new JTextField(text);                      
                    }
                    //editor.setEnabled(false);
                    boolean hasSecondary = !indep && m_secondaryModels != null && m_secondaryModels.containsKey(value);
                    editor.setFont(editor.getFont().deriveFont(hasSecondary ? Font.BOLD : Font.PLAIN));
                    editor.setToolTipText(hasSecondary ? m_secondaryModels.get(value).modelName : "");
                    editor.setBackground(new Color(244, 244, 244));
                    editor.setForeground(Color.BLACK);
                    c = editor;
              }
              else if (columnIndex == 1) {
                    Boolean indep = (Boolean) table.getValueAt(rowIndex, 2);
                    if (indep == null) indep = false;
                    String text = "";
                    if (value != null) {
                        text = value.toString();
                        String category = indep ? thePM.getIndepCategory(text) : thePM.getParamCategory(text);
                        String unit = indep ? thePM.getIndepUnit(text) : thePM.getParamUnit(text);
                        if (rowIndex == 0) {
                            category = thePM.getDepCategory();
                            unit = thePM.getDepUnit();
                        }
                        if (unit != null && !unit.isEmpty()) text = category + " -> " + unit;
                        else text = "";
                    }
                    JComponent editor = new JTextField(text);
                    if (rowIndex == 0) editor.setEnabled(false);
                    editor.setBackground(new Color(244, 244, 244));
                    editor.setForeground(Color.BLACK);
                    c = editor;
              }
              else if (columnIndex == 2) {
                  JCheckBox checkbox = new JCheckBox();
                  checkbox.setHorizontalAlignment(SwingConstants.CENTER);
                  checkbox.setText("");
                  if (rowIndex == 0) {checkbox.setEnabled(false); if (thePM.getLevel() == 1) checkbox.setText("Value");}
                  else {checkbox.setEnabled(true); }
                  checkbox.setSelected(value == null ? false : (Boolean) value);
                  if (checkbox.isSelected() && thePM.getLevel() == 1) checkbox.setText("Time");
                  checkbox.setBackground(Color.WHITE);
                  c = checkbox;
              }
              else if (columnIndex == 7) {
                  JTextField text = new JTextField();
                  text.setEnabled(true);
                  text.setText(value == null ? "" : value.toString());
                  text.setBackground(Color.WHITE);
                  c = text;
              }
              else {
                    DoubleTextField editor = new DoubleTextField(true);
                    if (value != null && value instanceof Double && !Double.isNaN((Double) value)) editor.setText(value.toString());
                    else editor.setText(null);
                    if (columnIndex == 3 || columnIndex == 4) { // Value, Error
                        Boolean indep = (Boolean) table.getValueAt(rowIndex, 2);
                        if (indep == null) indep = false;
                        editor.setEnabled(!indep);                        
                    }
                    if (columnIndex == 5 || columnIndex == 6) editor.setEnabled(true); // Min, Max
                    else if (rowIndex == 0) editor.setEnabled(false);
                    //if (isSelected)
                    c = editor;
              }
              
              try {
                  if (isSelected && hasFocus) {
                      c.setBackground(Color.CYAN);
                      c.setForeground(Color.BLACK);
                  }
                  else if (isSelected) {
                      UIDefaults uiDefaults = UIManager.getDefaults();
                      Color selBGColor = (Color) uiDefaults.get("Table.selectionBackground");
                      Color selFGColor = (Color) uiDefaults.get("Table.selectionForeground");
                      c.setBackground(selBGColor);            
                      c.setForeground(selFGColor);
                  }
              }
              catch (Exception e) {}
              
              c.setBorder(null);
              if (repaintAgain) {
                  table.repaint();
                  if (rowIndex == getRowCount() - 1 && columnIndex == getColumnCount() - 1) repaintAgain = false;
              }
              return c;
          }
        }
}