/* Copyright (c) 2011, Carl Burch. License information is located in the * com.cburch.logisim.Main source code and at www.cburch.com/logisim/. */ package com.cburch.logisim.gui.generic; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Dialog; import java.awt.Dimension; import java.awt.Font; import java.awt.Frame; import java.awt.Window; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.FocusEvent; import java.awt.event.FocusListener; import java.util.ArrayList; import java.util.EventObject; import java.util.LinkedList; import javax.swing.BorderFactory; import javax.swing.JComboBox; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.JTextField; import javax.swing.SwingConstants; import javax.swing.event.CellEditorListener; import javax.swing.event.ChangeEvent; import javax.swing.event.TableModelEvent; import javax.swing.event.TableModelListener; import javax.swing.table.TableCellEditor; import javax.swing.table.TableModel; import com.cburch.logisim.util.JDialogOk; import com.cburch.logisim.util.JInputComponent; import com.cburch.logisim.util.LocaleListener; import com.cburch.logisim.util.LocaleManager; public class AttrTable extends JPanel implements LocaleListener { private class CellEditor implements TableCellEditor, FocusListener, ActionListener { LinkedList<CellEditorListener> listeners = new LinkedList<CellEditorListener>(); AttrTableModelRow currentRow; Component currentEditor; // // ActionListener methods // @Override public void actionPerformed(ActionEvent e) { stopCellEditing(); } // // TableCellListener management // @Override public void addCellEditorListener(CellEditorListener l) { // Adds a listener to the list that's notified when the // editor stops, or cancels editing. listeners.add(l); } // // other TableCellEditor methods // @Override public void cancelCellEditing() { // Tells the editor to cancel editing and not accept any // partially edited value. fireEditingCanceled(); } public void fireEditingCanceled() { ChangeEvent e = new ChangeEvent(AttrTable.this); for (CellEditorListener l : new ArrayList<CellEditorListener>(listeners)) { l.editingCanceled(e); } } public void fireEditingStopped() { ChangeEvent e = new ChangeEvent(AttrTable.this); for (CellEditorListener l : new ArrayList<CellEditorListener>(listeners)) { l.editingStopped(e); } } @Override public void focusGained(FocusEvent e) { } // // FocusListener methods // @Override public void focusLost(FocusEvent e) { Object dst = e.getOppositeComponent(); if (dst instanceof Component) { Component p = (Component) dst; while (p != null && !(p instanceof Window)) { if (p == AttrTable.this) { // switch to another place in this table, // no problem return; } p = p.getParent(); } // focus transferred outside table; stop editing editor.stopCellEditing(); } } @Override public Object getCellEditorValue() { // Returns the value contained in the editor. Component comp = currentEditor; if (comp instanceof JTextField) { return ((JTextField) comp).getText(); } else if (comp instanceof JComboBox) { return ((JComboBox<?>) comp).getSelectedItem(); } else { return null; } } @Override public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int rowIndex, int columnIndex) { AttrTableModel attrModel = tableModel.attrModel; AttrTableModelRow row = attrModel.getRow(rowIndex); if (columnIndex == 0) { return new JLabel(row.getLabel()); } else { if (currentEditor != null) currentEditor.transferFocus(); Component editor = row.getEditor(parent); if (editor instanceof JComboBox) { ((JComboBox<?>) editor).addActionListener(this); editor.addFocusListener(this); } else if (editor instanceof JInputComponent) { JInputComponent input = (JInputComponent) editor; MyDialog dlog; Window parent = AttrTable.this.parent; if (parent instanceof Frame) { dlog = new MyDialog((Frame) parent, input); } else { dlog = new MyDialog((Dialog) parent, input); } dlog.setVisible(true); Object retval = dlog.getValue(); try { row.setValue(retval); } catch (AttrTableSetException e) { JOptionPane.showMessageDialog(parent, e.getMessage(), Strings.get("attributeChangeInvalidTitle"), JOptionPane.WARNING_MESSAGE); } editor = new JLabel(row.getValue()); } else { editor.addFocusListener(this); } currentRow = row; currentEditor = editor; return editor; } } @Override public boolean isCellEditable(EventObject anEvent) { // Asks the editor if it can start editing using anEvent. return true; } @Override public void removeCellEditorListener(CellEditorListener l) { // Removes a listener from the list that's notified listeners.remove(l); } @Override public boolean shouldSelectCell(EventObject anEvent) { // Returns true if the editing cell should be selected, // false otherwise. return true; } @Override public boolean stopCellEditing() { // Tells the editor to stop editing and accept any partially // edited value as the value of the editor. fireEditingStopped(); return true; } } private static class MyDialog extends JDialogOk { /** * */ private static final long serialVersionUID = -7689633170900231085L; JInputComponent input; Object value; public MyDialog(Dialog parent, JInputComponent input) { super(parent, Strings.get("attributeDialogTitle"), true); configure(input); } public MyDialog(Frame parent, JInputComponent input) { super(parent, Strings.get("attributeDialogTitle"), true); configure(input); } private void configure(JInputComponent input) { this.input = input; this.value = input.getValue(); // Thanks to Christophe Jacquet, who contributed a fix to this // so that when the dialog is resized, the component within it // is resized as well. (Tracker #2024479) JPanel p = new JPanel(new BorderLayout()); p.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); p.add((JComponent) input, BorderLayout.CENTER); getContentPane().add(p, BorderLayout.CENTER); pack(); } public Object getValue() { return value; } @Override public void okClicked() { value = input.getValue(); } } private static class NullAttrModel implements AttrTableModel { @Override public void addAttrTableModelListener(AttrTableModelListener listener) { } @Override public AttrTableModelRow getRow(int rowIndex) { return null; } @Override public int getRowCount() { return 0; } @Override public String getTitle() { return null; } @Override public void removeAttrTableModelListener(AttrTableModelListener listener) { } } private class TableModelAdapter implements TableModel, AttrTableModelListener { Window parent; LinkedList<TableModelListener> listeners; AttrTableModel attrModel; TableModelAdapter(Window parent, AttrTableModel attrModel) { this.parent = parent; this.listeners = new LinkedList<TableModelListener>(); this.attrModel = attrModel; } @Override public void addTableModelListener(TableModelListener l) { listeners.add(l); } @Override public void attrStructureChanged(AttrTableModelEvent e) { if (e.getSource() != attrModel) { attrModel.removeAttrTableModelListener(this); return; } TableCellEditor ed = table.getCellEditor(); if (ed != null) { ed.cancelCellEditing(); } fireTableChanged(); } // // AttrTableModelListener methods // @Override public void attrTitleChanged(AttrTableModelEvent e) { if (e.getSource() != attrModel) { attrModel.removeAttrTableModelListener(this); return; } updateTitle(); } @Override public void attrValueChanged(AttrTableModelEvent e) { if (e.getSource() != attrModel) { attrModel.removeAttrTableModelListener(this); return; } int row = e.getRowIndex(); TableCellEditor ed = table.getCellEditor(); if (row >= 0 && ed instanceof CellEditor && attrModel.getRow(row) == ((CellEditor) ed).currentRow) { ed.cancelCellEditing(); } fireTableChanged(); } void fireTableChanged() { TableModelEvent e = new TableModelEvent(this); for (TableModelListener l : new ArrayList<TableModelListener>(listeners)) { l.tableChanged(e); } } @Override public Class<?> getColumnClass(int columnIndex) { return String.class; } @Override public int getColumnCount() { return 2; } @Override public String getColumnName(int columnIndex) { if (columnIndex == 0) return "Attribute"; else return "Value"; } @Override public int getRowCount() { return attrModel.getRowCount(); } @Override public Object getValueAt(int rowIndex, int columnIndex) { if (columnIndex == 0) { return attrModel.getRow(rowIndex).getLabel(); } else { return attrModel.getRow(rowIndex).getValue(); } } @Override public boolean isCellEditable(int rowIndex, int columnIndex) { return columnIndex > 0 && attrModel.getRow(rowIndex).isValueEditable(); } @Override public void removeTableModelListener(TableModelListener l) { listeners.remove(l); } void setAttrTableModel(AttrTableModel value) { if (attrModel != value) { TableCellEditor editor = table.getCellEditor(); if (editor != null) { editor.cancelCellEditing(); } attrModel.removeAttrTableModelListener(this); attrModel = value; attrModel.addAttrTableModelListener(this); fireTableChanged(); } } @Override public void setValueAt(Object value, int rowIndex, int columnIndex) { if (columnIndex > 0) { try { attrModel.getRow(rowIndex).setValue(value); } catch (AttrTableSetException e) { JOptionPane.showMessageDialog(parent, e.getMessage(), Strings.get("attributeChangeInvalidTitle"), JOptionPane.WARNING_MESSAGE); } } } } private static class TitleLabel extends JLabel { /** * */ private static final long serialVersionUID = 8599788843622700019L; @Override public Dimension getMinimumSize() { Dimension ret = super.getMinimumSize(); return new Dimension(1, ret.height); } } /** * */ private static final long serialVersionUID = -529960152633563125L; private static final AttrTableModel NULL_ATTR_MODEL = new NullAttrModel(); private Window parent; private boolean titleEnabled; private JLabel title; private JTable table; private TableModelAdapter tableModel; private CellEditor editor = new CellEditor(); public AttrTable(Window parent) { super(new BorderLayout()); this.parent = parent; titleEnabled = true; title = new TitleLabel(); title.setHorizontalAlignment(SwingConstants.CENTER); title.setVerticalAlignment(SwingConstants.CENTER); tableModel = new TableModelAdapter(parent, NULL_ATTR_MODEL); table = new JTable(tableModel); table.setDefaultEditor(Object.class, editor); table.setTableHeader(null); table.setRowHeight(20); Font baseFont = title.getFont(); int titleSize = Math.round(baseFont.getSize() * 1.2f); Font titleFont = baseFont.deriveFont((float) titleSize).deriveFont(Font.BOLD); title.setFont(titleFont); Color bgColor = new Color(240, 240, 240); setBackground(bgColor); table.setBackground(bgColor); Object renderer = table.getDefaultRenderer(String.class); if (renderer instanceof JComponent) { ((JComponent) renderer).setBackground(Color.WHITE); } JScrollPane tableScroll = new JScrollPane(table); this.add(title, BorderLayout.PAGE_START); this.add(tableScroll, BorderLayout.CENTER); LocaleManager.addLocaleListener(this); localeChanged(); } public AttrTableModel getAttrTableModel() { return tableModel.attrModel; } public boolean getTitleEnabled() { return titleEnabled; } @Override public void localeChanged() { updateTitle(); tableModel.fireTableChanged(); } public void setAttrTableModel(AttrTableModel value) { tableModel.setAttrTableModel(value == null ? NULL_ATTR_MODEL : value); updateTitle(); } public void setTitleEnabled(boolean value) { titleEnabled = value; updateTitle(); } private void updateTitle() { if (titleEnabled) { String text = tableModel.attrModel.getTitle(); if (text == null) { title.setVisible(false); } else { title.setText(text); title.setVisible(true); } } else { title.setVisible(false); } } }