/*******************************************************************************
* Copyright 2016 Observational Health Data Sciences and Informatics
*
* This file is part of WhiteRabbit
*
* 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.
******************************************************************************/
package org.ohdsi.rabbitInAHat;
import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.BorderFactory;
import javax.swing.InputMap;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextArea;
import javax.swing.KeyStroke;
import javax.swing.UIManager;
import javax.swing.border.MatteBorder;
import javax.swing.event.ChangeEvent;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.TableColumnModelEvent;
import javax.swing.event.TableColumnModelListener;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.event.UndoableEditEvent;
import javax.swing.event.UndoableEditListener;
import javax.swing.table.TableModel;
import javax.swing.text.Document;
import javax.swing.undo.CannotUndoException;
import javax.swing.undo.UndoManager;
import org.ohdsi.rabbitInAHat.dataModel.Field;
import org.ohdsi.rabbitInAHat.dataModel.ItemToItemMap;
import org.ohdsi.rabbitInAHat.dataModel.Table;
import org.ohdsi.utilities.StringUtilities;
import org.ohdsi.rabbitInAHat.dataModel.TableCellLongTextRenderer;
public class DetailsPanel extends JPanel implements DetailsListener {
public static Font font = new Font("default", Font.PLAIN, 18);
private static final long serialVersionUID = 4477553676983048468L;
private Object object;
private TablePanel tablePanel;
private FieldPanel fieldPanel;
private ItemToItemMapPanel itemToItemMapPanel;
private CardLayout cardLayout = new CardLayout();
private UndoManager undoManager;
public DetailsPanel() {
UIManager.put("Label.font", font);
setLayout(cardLayout);
tablePanel = new TablePanel();
add(tablePanel, Table.class.getName());
fieldPanel = new FieldPanel();
add(fieldPanel, Field.class.getName());
itemToItemMapPanel = new ItemToItemMapPanel();
add(itemToItemMapPanel, ItemToItemMap.class.getName());
JPanel nullPanel = new JPanel();
add(nullPanel, "");
cardLayout.show(this, "");
undoManager = new UndoManager();
}
@Override
public void showDetails(Object object) {
this.object = object;
if (object instanceof Table) {
tablePanel.showTable((Table) object);
tablePanel.updateRowHeights();
cardLayout.show(this, Table.class.getName());
} else if (object instanceof Field) {
fieldPanel.showField((Field) object);
cardLayout.show(this, Field.class.getName());
} else if (object instanceof ItemToItemMap) {
itemToItemMapPanel.showItemToItemMap((ItemToItemMap) object);
cardLayout.show(this, ItemToItemMap.class.getName());
} else
cardLayout.show(this, "");
// Discard edits made by showing a new details view
undoManager.discardAllEdits();
}
public void refresh() {
showDetails(object);
}
private void addUndoToTextArea(JTextArea jta){
Document doc = jta.getDocument();
doc.addUndoableEditListener(new UndoableEditListener() {
@Override
public void undoableEditHappened(UndoableEditEvent e) {
undoManager.addEdit(e.getEdit());
}
});
InputMap im = jta.getInputMap(JComponent.WHEN_FOCUSED);
ActionMap am = jta.getActionMap();
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_Z, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()), "Undo");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_Y, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()), "Redo");
am.put("Undo", new AbstractAction() {
/**
*
*/
private static final long serialVersionUID = -3363877112423623107L;
@Override
public void actionPerformed(ActionEvent e) {
try {
if (undoManager.canUndo()) {
undoManager.undo();
}
} catch (CannotUndoException exp) {
exp.printStackTrace();
}
}
});
am.put("Redo", new AbstractAction() {
/**
*
*/
private static final long serialVersionUID = -5581878642285644039L;
@Override
public void actionPerformed(ActionEvent e) {
try {
if (undoManager.canRedo()) {
undoManager.redo();
}
} catch (CannotUndoException exp) {
exp.printStackTrace();
}
}
});
}
private class TablePanel extends JPanel implements DocumentListener {
private static final long serialVersionUID = -4393026616049677944L;
private Table table;
private JLabel nameLabel = new JLabel("");
private JLabel rowCountLabel = new JLabel("");
private SimpleTableModel fieldTable = new SimpleTableModel("Field", "Type","Description");
private JTextArea commentsArea = new JTextArea();
private JTable displayTable = new JTable(fieldTable);
public TablePanel() {
setLayout(new BorderLayout());
JPanel generalInfoPanel = new JPanel();
generalInfoPanel.setLayout(new GridLayout(0, 2));
generalInfoPanel.setBorder(BorderFactory.createTitledBorder("General information"));
generalInfoPanel.add(new JLabel("Table name: "));
generalInfoPanel.add(nameLabel);
generalInfoPanel.add(new JLabel("Number of rows: "));
generalInfoPanel.add(rowCountLabel);
add(generalInfoPanel, BorderLayout.NORTH);
JScrollPane fieldListPanel = new JScrollPane(displayTable);
// Updates row heights when column widths change
displayTable.getColumnModel().addColumnModelListener(new TableColumnModelListener(){
@Override
public void columnMarginChanged(ChangeEvent e) {
updateRowHeights();
}
@Override
public void columnMoved(TableColumnModelEvent e){
}
@Override
public void columnAdded(TableColumnModelEvent e) {
// TODO Auto-generated method stub
}
@Override
public void columnRemoved(TableColumnModelEvent e) {
// TODO Auto-generated method stub
}
@Override
public void columnSelectionChanged(ListSelectionEvent e) {
// TODO Auto-generated method stub
}
});
displayTable.setFont(font);
// Set cell renderer that wraps content
for (int c = 0; c < displayTable.getColumnCount(); c++){
displayTable.getColumnModel().getColumn(c).setCellRenderer(new TableCellLongTextRenderer());
}
fieldListPanel.setBorder(BorderFactory.createTitledBorder("Fields"));
add(fieldListPanel, BorderLayout.CENTER);
JScrollPane commentsPanel = new JScrollPane(commentsArea);
commentsArea.setFont(font);
commentsArea.getDocument().addDocumentListener(this);
commentsArea.setWrapStyleWord(true);
commentsArea.setLineWrap(true);
commentsPanel.setBorder(BorderFactory.createTitledBorder("Comments"));
commentsPanel.setPreferredSize(new Dimension(100, 200));
add(commentsPanel, BorderLayout.SOUTH);
}
private void updateRowHeights() {
/*
* Auto adjust the height of rows in a JTable.
* The only way to know the row height for sure is to render each cell
* to determine the rendered height. After your table is populated with
* data you can do:
*
*/
for (int row = 0; row < displayTable.getRowCount(); row++) {
int rowHeight = displayTable.getRowHeight();
for (int column = 0; column < displayTable.getColumnCount(); column++)
{
Component comp = displayTable.prepareRenderer(displayTable.getCellRenderer(row, column), row, column);
rowHeight = Math.max(rowHeight, comp.getPreferredSize().height);
}
displayTable.setRowHeight(row, rowHeight);
}
}
public void showTable(Table table) {
this.table = table;
nameLabel.setText(table.getName());
DecimalFormat formatter = new DecimalFormat("#,###");
rowCountLabel.setText(formatter.format(table.getRowCount()));
fieldTable.clear();
for (Field field : table.getFields()){
fieldTable.add(field.outputName(), field.getType(),field.getDescription());
}
commentsArea.setText(table.getComment());
}
@Override
public void changedUpdate(DocumentEvent e) {
table.setComment(commentsArea.getText());
}
@Override
public void insertUpdate(DocumentEvent e) {
table.setComment(commentsArea.getText());
}
@Override
public void removeUpdate(DocumentEvent e) {
table.setComment(commentsArea.getText());
}
}
private class FieldPanel extends JPanel implements DocumentListener {
private static final long serialVersionUID = -4393026616049677944L;
private JLabel nameLabel = new JLabel("");
private JLabel rowCountLabel = new JLabel("");
private DescriptionTextArea description = new DescriptionTextArea ("");
private SimpleTableModel valueTable = new SimpleTableModel("Value", "Frequency", "Percent of Total (%)");
private JTextArea commentsArea = new JTextArea();
private Field field;
public FieldPanel() {
setLayout(new BorderLayout());
JPanel generalInfoPanel = new JPanel();
generalInfoPanel.setLayout(new BorderLayout(5,5));
generalInfoPanel.setBorder(BorderFactory.createTitledBorder("General information"));
JPanel fieldInfo = new JPanel();
fieldInfo.setLayout(new GridLayout(0,2));
fieldInfo.add(new JLabel("Field name: "));
fieldInfo.add(nameLabel);
fieldInfo.add(new JLabel("Field type: "));
fieldInfo.add(rowCountLabel);
generalInfoPanel.add(fieldInfo,BorderLayout.NORTH);
JPanel descriptionInfo = new JPanel();
descriptionInfo.setLayout(new GridLayout(0,2));
descriptionInfo.add(new JLabel("Description: "));
descriptionInfo.add(description);
generalInfoPanel.add(descriptionInfo,BorderLayout.SOUTH);
add(generalInfoPanel, BorderLayout.NORTH);
JTable table = new JTable(valueTable);
JScrollPane fieldListPanel = new JScrollPane(table);
table.setFont(font);
table.setRowHeight(24);
table.setBorder(new MatteBorder(1, 0, 1, 0, Color.BLACK));
fieldListPanel.setBorder(BorderFactory.createTitledBorder("Fields"));
add(fieldListPanel, BorderLayout.CENTER);
JScrollPane commentsPanel = new JScrollPane(commentsArea);
commentsArea.setFont(font);
commentsArea.getDocument().addDocumentListener(this);
commentsArea.setWrapStyleWord(true);
commentsArea.setLineWrap(true);
addUndoToTextArea(commentsArea);
commentsPanel.setBorder(BorderFactory.createTitledBorder("Comments"));
commentsPanel.setPreferredSize(new Dimension(100, 200));
add(commentsPanel, BorderLayout.SOUTH);
}
public void showField(Field field) {
this.field = field;
nameLabel.setText(field.getName());
rowCountLabel.setText(field.getType());
description.setText(field.getDescription());
// Hide description if it's empty
description.getParent().setVisible(!description.getText().isEmpty());
valueTable.clear();
if (field.getValueCounts() != null) {
double valueCountTotal = 0.0;
for (String[] total : field.getValueCounts()) {
String temp = total[1];
if (StringUtilities.isNumber(temp)) {
double valueCountTemp = Double.parseDouble(temp);
valueCountTotal += valueCountTemp;
}
}
DecimalFormat formatter = new DecimalFormat("#,###");
DecimalFormat formatterPercent = new DecimalFormat("#,##0.0");
for (String[] valueCount : field.getValueCounts()) {
String nr = valueCount[1];
String vp = "";
if (StringUtilities.isNumber(nr)) {
double number = Double.parseDouble(nr);
nr = formatter.format(number);
double valueCountPercent = number / valueCountTotal * 100;
if (valueCountPercent < 0.1) {
vp = "< 0.1";
}
else if (valueCountPercent > 99) {
vp = "> 99.0";
}
else {
vp = formatterPercent.format(valueCountPercent);
}
}
valueTable.add(valueCount[0], nr, vp);
}
}
commentsArea.setText(field.getComment());
}
@Override
public void changedUpdate(DocumentEvent e) {
field.setComment(commentsArea.getText());
}
@Override
public void insertUpdate(DocumentEvent e) {
field.setComment(commentsArea.getText());
}
@Override
public void removeUpdate(DocumentEvent e) {
field.setComment(commentsArea.getText());
}
}
private class ItemToItemMapPanel extends JPanel implements DocumentListener {
private static final long serialVersionUID = -4393026616049677944L;
private JLabel sourceLabel = new JLabel("");
private JLabel targetLabel = new JLabel("");
private JTextArea logicArea = new JTextArea();
private JTextArea commentsArea = new JTextArea();
// private FlexTable testTable = new FlexTable();
private ItemToItemMap itemToItemMap;
public ItemToItemMapPanel() {
setLayout(new BorderLayout());
JPanel generalInfoPanel = new JPanel();
generalInfoPanel.setLayout(new GridLayout(0, 2));
generalInfoPanel.setBorder(BorderFactory.createTitledBorder("General information"));
generalInfoPanel.add(new JLabel("Source: "));
generalInfoPanel.add(sourceLabel);
generalInfoPanel.add(new JLabel("Target: "));
generalInfoPanel.add(targetLabel);
add(generalInfoPanel, BorderLayout.NORTH);
JScrollPane logicPanel = new JScrollPane(logicArea);
logicArea.setFont(font);
logicArea.getDocument().addDocumentListener(this);
logicArea.setWrapStyleWord(true);
logicArea.setLineWrap(true);
addUndoToTextArea(logicArea);
logicPanel.setBorder(BorderFactory.createTitledBorder("Logic"));
logicPanel.setPreferredSize(new Dimension(100, 200));
add(logicPanel, BorderLayout.CENTER);
JScrollPane commentsPanel = new JScrollPane(commentsArea);
commentsArea.setFont(font);
commentsArea.getDocument().addDocumentListener(this);
commentsArea.setWrapStyleWord(true);
commentsArea.setLineWrap(true);
addUndoToTextArea(commentsArea);
commentsPanel.setBorder(BorderFactory.createTitledBorder("Comments"));
commentsPanel.setPreferredSize(new Dimension(100, 200));
add(commentsPanel, BorderLayout.SOUTH);
// testTable.setMinimumSize(new Dimension(200, 200));
// JScrollPane testPanel = new JScrollPane(testTable);
// testPanel.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
// add(testPanel, BorderLayout.SOUTH);
}
public void showItemToItemMap(ItemToItemMap itemToItemMap) {
this.itemToItemMap = itemToItemMap;
sourceLabel.setText(itemToItemMap.getSourceItem().toString());
targetLabel.setText(itemToItemMap.getTargetItem().toString());
logicArea.setText(itemToItemMap.getLogic());
commentsArea.setText(itemToItemMap.getComment());
}
@Override
public void changedUpdate(DocumentEvent e) {
if (e.getDocument() == logicArea.getDocument())
itemToItemMap.setLogic(logicArea.getText());
else
itemToItemMap.setComment(commentsArea.getText());
}
@Override
public void insertUpdate(DocumentEvent e) {
if (e.getDocument() == logicArea.getDocument())
itemToItemMap.setLogic(logicArea.getText());
else
itemToItemMap.setComment(commentsArea.getText());
}
@Override
public void removeUpdate(DocumentEvent e) {
if (e.getDocument() == logicArea.getDocument())
itemToItemMap.setLogic(logicArea.getText());
else
itemToItemMap.setComment(commentsArea.getText());
}
}
private class SimpleTableModel implements TableModel {
private List<TableModelListener> listeners = new ArrayList<TableModelListener>();
private List<List<String>> data = new ArrayList<List<String>>();
private String[] columnNames;
public void clear() {
data.clear();
notifyListeners();
}
private void notifyListeners() {
for (TableModelListener listener : listeners)
listener.tableChanged(new TableModelEvent(this));
}
public void add(String... values) {
List<String> row = new ArrayList<String>(values.length);
for (int i = 0; i < values.length; i++)
row.add(values[i]);
data.add(row);
notifyListeners();
}
public SimpleTableModel(String... columnNames) {
this.columnNames = columnNames;
}
@Override
public void addTableModelListener(TableModelListener l) {
listeners.add(l);
}
@Override
public Class<?> getColumnClass(int columnIndex) {
return String.class;
}
@Override
public int getColumnCount() {
return columnNames.length;
}
@Override
public String getColumnName(int columnIndex) {
return columnNames[columnIndex];
}
@Override
public int getRowCount() {
return data.size();
}
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
return data.get(rowIndex).get(columnIndex);
}
@Override
public boolean isCellEditable(int rowIndex, int columnIndex) {
return false;
}
@Override
public void removeTableModelListener(TableModelListener l) {
listeners.remove(l);
}
@Override
public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
}
}
}