package gov.nasa.jpl.mbee.mdk.docgen.table; import au.com.bytecode.opencsv.CSVReader; import au.com.bytecode.opencsv.CSVWriter; import com.nomagic.magicdraw.core.Application; import com.nomagic.magicdraw.core.GUILog; import com.nomagic.magicdraw.core.Project; import com.nomagic.magicdraw.openapi.uml.SessionManager; import com.nomagic.magicdraw.ui.dialogs.MDDialogParentProvider; import com.nomagic.magicdraw.uml.BaseElement; import com.nomagic.magicdraw.uml2.util.UML2ModelUtil; import com.nomagic.uml2.ext.magicdraw.classes.mdkernel.Element; import com.nomagic.uml2.ext.magicdraw.classes.mdkernel.NamedElement; import com.nomagic.uml2.ext.magicdraw.classes.mdkernel.Property; import gov.nasa.jpl.mbee.mdk.api.incubating.convert.Converters; import gov.nasa.jpl.mbee.mdk.util.Utils; import javax.swing.*; import javax.swing.table.DefaultTableCellRenderer; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Set; /** * Make an editable table.<br/> * <p> * Takes a table model consisting of Objects. Objects can be Strings or * magicdraw Elements. All rows in the model must have the same number of * columns * </p> * <p> * Takes a list of strings for headers. number of headers must equal number of * columns * </p> * <p> * Takes a model of boolean indicating which of the objects in the model should * be editable. (this has no relation to whether the element itself is actually * editable, that will be checked by the table when being edited) * </p> * <p> * Takes a model of PropertyEnum indicating whether the name (or value in the * case of magicdraw Property) should be shown/edited * </p> * <p> * The Objects for the table model and headers must be present, editable and * propertyEnum can be omitted. By default, if there's no instruction, names * will be shown/edited for NamedElements and nothing can be edited * </p> * * @author dlam */ @SuppressWarnings("serial") public class EditableTable extends JDialog { private List<List<Object>> model; private List<String> headers; private List<List<Boolean>> editable; private List<Boolean> editableCol; private List<PropertyEnum> whatToShowCol; private List<List<PropertyEnum>> whatToShow; private JTable jtable; private int precision; private static String csvSeparator = ","; // Keep history of // selected separator /** * @param title Title of the table * @param m the table model, Object can be String or magicdraw elements, * all rows must have the same amount of columns * @param headers list of strings, size must match number of columns * @param editable can be null, whether each cell should be editable. (this does * not mean whether the element is actually editable, that'll be * checked when actually being edited) * @param e can be null, what to show/edit for each cell. Only two choices * right now, name for NamedElements or value for Properties. * @param precision can be null, this is applicable to if you want to show values * on properties and they're floating numbers, the precision * indicates how mnay decimal places to show */ public EditableTable(String title, List<List<Object>> m, List<String> headers, List<List<Boolean>> editable, List<List<PropertyEnum>> e, Integer precision) { super(MDDialogParentProvider.getProvider().getDialogParent()); this.model = m; this.headers = headers; this.editable = editable; this.whatToShow = e; this.setTitle(title); if (precision == null) { this.precision = -1; } else { this.precision = precision; } } /** * Instead of having to make a n x m table of booleans, if your columns are * either editable or not (usually the case), use this to set whether * columns are editable. instance the table with null and then use this to * set column wide * * @param editableCol */ public void setEditableCol(List<Boolean> editableCol) { this.editableCol = editableCol; } /** * Instead of having to make a n x m table of what to show, specify by * columns instead. you can pass in null when instancing the table and then * use this to set column wide * * @param whatToShowCol */ public void setWhatToShowCol(List<PropertyEnum> whatToShowCol) { this.whatToShowCol = whatToShowCol; } /** * this must be called by your script before your script ends */ public void prepareTable() { EditableTableModel tmodel = new EditableTableModel(model, headers, editable, whatToShow, editableCol, whatToShowCol, precision); jtable = new JTable(tmodel); jtable.setDefaultRenderer(String.class, new ChangedTableCellRenderer(tmodel)); jtable.getTableHeader().setPreferredSize( new Dimension(jtable.getColumnModel().getTotalColumnWidth(), 45)); jtable.getTableHeader().setReorderingAllowed(false); jtable.setGridColor(Color.BLACK); jtable.setRowHeight(20); jtable.setSize(new Dimension(300, 120)); JScrollPane content = new JScrollPane(jtable); this.getContentPane().add(content, BorderLayout.CENTER); JPanel csv = new JPanel(); JButton exportButton = new JButton("Export to CSV"); exportButton.addActionListener(new ExportCSVAction(tmodel)); csv.add(exportButton); JButton importButton = new JButton("Import from CSV"); importButton.addActionListener(new ImportCSVAction(tmodel)); csv.add(importButton); JButton refreshButton = new JButton("Refresh"); refreshButton.addActionListener(new RefreshAction(tmodel)); csv.add(refreshButton); this.getContentPane().add(csv, BorderLayout.PAGE_END); } class RefreshAction implements ActionListener { EditableTableModel ntable; public RefreshAction(EditableTableModel t) { ntable = t; } @Override public void actionPerformed(ActionEvent arg0) { ntable.fireTableDataChanged(); } } class ImportCSVAction implements ActionListener { EditableTableModel ntable; public ImportCSVAction(EditableTableModel t) { ntable = t; } @Override public void actionPerformed(ActionEvent arg0) { GUILog gl = Application.getInstance().getGUILog(); String separator = getSeparator(); if (separator == null) { return; } JFileChooser choose = new JFileChooser(); choose.setDialogTitle("Open csv file"); choose.setFileSelectionMode(JFileChooser.FILES_ONLY); int retval = choose.showOpenDialog(null); if (retval == JFileChooser.APPROVE_OPTION) { if (choose.getSelectedFile() != null) { File savefile = choose.getSelectedFile(); try { SessionManager.getInstance().createSession("change"); CSVReader reader = new CSVReader(new FileReader(savefile), separator.charAt(0)); importFromCsv(reader); reader.close(); SessionManager.getInstance().closeSession(); gl.log("import succeeded"); } catch (IOException ex) { gl.log("import failed"); SessionManager.getInstance().cancelSession(); gl.log(ex.getMessage()); for (StackTraceElement s : ex.getStackTrace()) { gl.log("\t" + s.toString()); } } } } } private void importFromCsv(CSVReader reader) throws IOException { GUILog gl = Application.getInstance().getGUILog(); Project project = Application.getInstance().getProject(); List<List<PropertyEnum>> what = ntable.getWhatToChange(); List<PropertyEnum> whatCol = ntable.getWhatToChangeCol(); String[] line = reader.readNext(); // ignore header line = reader.readNext(); int row = 0; boolean positionErrorSeen = false; boolean notEditableErrorSeen = false; while (line != null) { String[] props = line; // gl.log("line: " + props.toString()); int col = 0; for (int c = 0; c < props.length; c = c + 2) { if (props[c].isEmpty()) { col++; continue; } PropertyEnum whatToChange = null; BaseElement e = Converters.getIdToElementConverter() .apply(props[c], project); String value = props[c + 1]; if (what != null && what.size() > row && what.get(row).size() > col) { whatToChange = what.get(row).get(col); } if (whatToChange == null && whatCol != null && whatCol.size() > col) { whatToChange = whatCol.get(col); } whatToChange = whatToChange == null ? PropertyEnum.NAME : whatToChange; Object el = null; if (model.size() > row && model.get(row).size() > col) { el = model.get(row).get(col); } if (e != null) { if (el instanceof Element && Converters.getElementToIdConverter().apply((Element) el).equals(props[c])) { if (e.isEditable()) { try { String curValue = (String) ntable.getValueAt(row, col); if (e instanceof Property && whatToChange == PropertyEnum.VALUE) { if (UML2ModelUtil.getDefault(((Property) e)) == null || !UML2ModelUtil.getDefault(((Property) e)).equals(value)) { if (value.isEmpty()) { int ok = JOptionPane.showConfirmDialog(null, "You're about to set the value of " + ((NamedElement) e).getQualifiedName() + " to empty, are you sure?", "Confirm", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); if (ok == JOptionPane.YES_OPTION) { Utils.setPropertyValue((Property) e, value); ntable.setElementChanged((Element) e); } } else { // only mark cell as changed if // value changed if (!curValue.equals(value)) { gl.log("changing table element [" + row + "][" + col + "] " + ((NamedElement) e).getQualifiedName() + "'s value from {" + curValue + "} to {" + value + "}"); Utils.setPropertyValue((Property) e, value); ntable.setElementChanged((Element) e); } } } } else if (e instanceof NamedElement && whatToChange == PropertyEnum.NAME) { if (!((NamedElement) e).getName().equals(value)) { if (value.isEmpty()) { int ok = JOptionPane.showConfirmDialog(null, "You're about to set the name of " + ((NamedElement) e).getQualifiedName() + " to empty, are you sure?", "Confirm", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); if (ok == JOptionPane.YES_OPTION) { ((NamedElement) e).setName(value); ntable.setElementChanged((Element) e); } } else { // only mark cell as changed if // value changed if (!curValue.equals(value)) { gl.log("changing table element [" + row + "][" + col + "] " + ((NamedElement) e).getQualifiedName() + "'s name from {" + curValue + "} to {" + value + "}"); ((NamedElement) e).setName(value); ntable.setElementChanged((Element) e); } } } } } catch (Exception ex) { gl.log(ex.getMessage()); for (StackTraceElement s : ex.getStackTrace()) { gl.log("\t" + s.toString()); } ex.printStackTrace(); } } else if (e instanceof NamedElement) { if (!notEditableErrorSeen) { JOptionPane .showMessageDialog( null, "Element " + ((NamedElement) e).getQualifiedName() + " is not editable! (Further similar errors can be seen in the log"); notEditableErrorSeen = true; } gl.log("Element " + ((NamedElement) e).getQualifiedName() + " is not editable!"); } } else { if (!positionErrorSeen) { JOptionPane .showMessageDialog( null, "Element with ID " + props[c] + " is not in the same position as what's in the table! (See log for further similar errors)"); positionErrorSeen = true; } gl.log("Element with ID " + props[c] + " is not in the same position as what's in the table!"); if (el instanceof NamedElement) { gl.log("Table element: " + ((NamedElement) el).getQualifiedName() + ", Importing element: " + ((NamedElement) e).getQualifiedName()); } } } else { JOptionPane.showMessageDialog(null, "Element with ID " + props[c] + " not found."); gl.log("Element with ID " + props[c] + " not found."); } col++; } line = reader.readNext(); row++; } } } class ExportCSVAction implements ActionListener { EditableTableModel ntable; public ExportCSVAction(EditableTableModel t) { ntable = t; } @Override public void actionPerformed(ActionEvent arg0) { GUILog gl = Application.getInstance().getGUILog(); String separator = getSeparator(); if (separator == null) { return; } JFileChooser choose = new JFileChooser(); choose.setDialogTitle("Save to csv..."); int retval = choose.showSaveDialog(null); if (retval == JFileChooser.APPROVE_OPTION) { if (choose.getSelectedFile() != null) { File savefile = choose.getSelectedFile(); try { CSVWriter csvWriter = new CSVWriter(new FileWriter(savefile), separator.charAt(0)); exportToCsv(csvWriter); csvWriter.close(); gl.log("export succeeded"); } catch (IOException ex) { gl.log("export failed"); gl.log(ex.getMessage()); for (StackTraceElement s : ex.getStackTrace()) { gl.log("\t" + s.toString()); } } } } } private void exportToCsv(CSVWriter w) throws IOException { List<List<Object>> m = ntable.getModel(); int rows = ntable.getRowCount(); int cols = ntable.getColumnCount(); List<String> blah = new ArrayList<String>(); for (int i = 0; i < cols; i++) { blah.add(""); blah.add(ntable.getColumnName(i)); } w.writeNext(blah.toArray(new String[0])); for (int i = 0; i < rows; i++) { List<String> s = new ArrayList<String>(); for (int j = 0; j < cols; j++) { Object mdo = m.get(i).get(j); if (mdo != null && mdo instanceof Element) { s.add(Converters.getElementToIdConverter().apply((Element) mdo)); } else { s.add(""); } String val = (String) ntable.getValueAt(i, j); s.add(val); } w.writeNext(s.toArray(new String[0])); } } } /** * this may be called after prepareTable is called, if you want the jtable * to mess with * * @return */ public JTable getTable() { return jtable; } /** * don't call this in your script */ public void showTable() { this.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); this.setLocationRelativeTo(null); this.setSize(300, 120); // this.setAlwaysOnTop(true); this.pack(); this.setVisible(true); } class ChangedTableCellRenderer extends DefaultTableCellRenderer { private EditableTableModel table; public ChangedTableCellRenderer(EditableTableModel t) { super(); table = t; } @Override public Component getTableCellRendererComponent(JTable t, Object value, boolean isSelected, boolean hasFocus, int row, int column) { Component c = super.getTableCellRendererComponent(t, value, isSelected, hasFocus, row, column); Object e = table.getObjectAt(row, column); Set<Object> changed = table.getChanged(); if (e != null && changed.contains(e) && (e instanceof Element)) { c.setBackground(Color.yellow); } else { c.setBackground(Color.white); } c.setForeground(Color.black); if (e != null && e instanceof Property && ((Property) e).isDerived()) { if (changed.contains(e)) { c.setBackground(Color.lightGray); } else { c.setBackground(Color.gray); } } return c; } } /** * Method to bring up input dialog to query user for the delimiter type * * @return The character delimiter */ public static String getSeparator() { String separator = JOptionPane.showInputDialog("Provide separator (use only a single character).", csvSeparator); if (separator != null) { csvSeparator = separator; } return separator; } }