Project: freemind
/*FreeMind - A Program for creating and viewing Mindmaps
 *Copyright (C) 2000-2006 Joerg Mueller, Daniel Polansky, Christian Foltin, Dimitri Polivaev and others. 
 * 
 *See COPYING for Details 
 * 
 *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 2 
 *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, write to the Free Software 
 *Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA. 
 */
/*
 * Created on 12.06.2005 
 * 
 */
 
package freemind.view.mindmapview.attributeview; 
 
import java.awt.Component; 
import java.awt.Dimension; 
import java.awt.EventQueue; 
import java.awt.Font; 
import java.awt.event.FocusEvent; 
import java.awt.event.FocusListener; 
import java.awt.event.KeyEvent; 
import java.awt.event.MouseAdapter; 
import java.awt.event.MouseEvent; 
import java.awt.event.MouseListener; 
import java.util.logging.Logger; 
 
import javax.swing.ComboBoxEditor; 
import javax.swing.ComboBoxModel; 
import javax.swing.DefaultCellEditor; 
import javax.swing.DefaultComboBoxModel; 
import javax.swing.JComboBox; 
import javax.swing.JTable; 
import javax.swing.JViewport; 
import javax.swing.KeyStroke; 
import javax.swing.SwingUtilities; 
import javax.swing.event.TableModelEvent; 
import javax.swing.table.JTableHeader; 
import javax.swing.table.TableCellEditor; 
import javax.swing.table.TableCellRenderer; 
import javax.swing.table.TableModel; 
 
import freemind.main.Resources; 
import freemind.modes.MindMapNode; 
import freemind.modes.attributes.AttributeController; 
import freemind.modes.attributes.AttributeRegistry; 
import freemind.modes.attributes.AttributeTableLayoutModel; 
import freemind.modes.attributes.AttributeTableModel; 
import freemind.modes.attributes.ColumnWidthChangeEvent; 
import freemind.modes.attributes.ColumnWidthChangeListener; 
import freemind.view.mindmapview.MapView; 
import freemind.view.mindmapview.NodeView; 
 
/**
 * @author dimitri 12.06.2005 
 */
 
public class AttributeTable extends JTable implements ColumnWidthChangeListener { 
 private static final int MAX_HEIGTH = 300
 private static final int MAX_WIDTH = 600
 
 static private class MyFocusListener implements FocusListener { 
  private AttributeTable focusedTable; 
 
  /*
   * (non-Javadoc) 
   *  
   * @see 
   * java.awt.event.FocusListener#focusGained(java.awt.event.FocusEvent) 
   */
 
  public void focusGained(final FocusEvent event) { 
   final Component source = (Component) event.getSource(); 
   final Component oppositeComponent = event.getOppositeComponent(); 
   if (source instanceof AttributeTable) { 
    focusedTable = (AttributeTable) source; 
   else { 
    focusedTable = (AttributeTable) SwingUtilities 
      .getAncestorOfClass(AttributeTable.class, source); 
   
   EventQueue.invokeLater(new Runnable() { 
    public void run() { 
     if (focusedTable != null) { 
      final Component newNodeViewInFocus = SwingUtilities 
        .getAncestorOfClass(NodeView.class
          focusedTable); 
      if (newNodeViewInFocus != null) { 
       NodeView viewer = (NodeView) newNodeViewInFocus; 
       if (viewer != viewer.getMap().getSelected()) { 
        viewer.getMap().selectAsTheOnlyOneSelected( 
          viewer, false); 
       
      
     
    
   }); 
  
 
  /*
   * (non-Javadoc) 
   *  
   * @see 
   * java.awt.event.FocusListener#focusLost(java.awt.event.FocusEvent) 
   */
 
  public void focusLost(FocusEvent event) { 
   Component oppositeComponent = event.getOppositeComponent(); 
   Component newTable = SwingUtilities.getAncestorOfClass( 
     AttributeTable.class, oppositeComponent); 
   if (focusedTable != null && focusedTable != newTable) { 
    if (focusedTable.isEditing()) { 
     focusedTable.getCellEditor().stopCellEditing(); 
    
    if (!focusedTable.attributeView.isPopupShown()) { 
     final AttributeView attributeView = focusedTable 
       .getAttributeView(); 
     final String currentAttributeViewType = attributeView 
       .getNode().getMap().getRegistry().getAttributes() 
       .getAttributeViewType(); 
     if (attributeView.getViewType() != currentAttributeViewType) { 
      attributeView.stateChanged(null); 
     
    
    focusedTable = null
   
  
 
 
 
 static private class HeaderMouseListener extends MouseAdapter { 
  public void mouseReleased(MouseEvent e) { 
   JTableHeader header = (JTableHeader) e.getSource(); 
   AttributeTable table = (AttributeTable) header.getTable(); 
   float zoom = table.attributeView.getMapView().getZoom(); 
   Dimension preferredScrollableViewportSize = table 
     .getPreferredScrollableViewportSize(); 
   JViewport port = (JViewport) table.getParent(); 
   Dimension extentSize = port.getExtentSize(); 
   if (preferredScrollableViewportSize.width != extentSize.width) { 
    AttributeTableModel model = (AttributeTableModel) table 
      .getModel(); 
    for (int col = 0; col < table.getColumnCount(); col++) { 
     int modelColumnWidth = model.getColumnWidth(col); 
     int currentColumnWidth = (int) (table.getColumnModel() 
       .getColumn(col).getWidth() / zoom); 
     if (modelColumnWidth != currentColumnWidth) { 
      model.setColumnWidth(col, currentColumnWidth); 
     
    
   
  
 
 
 static private MyFocusListener focusListener = new MyFocusListener(); 
 static private MouseListener componentListener = new HeaderMouseListener(); 
 private int highRowIndex = 0
 static private ComboBoxModel defaultComboBoxModel = null
 static private AttributeTableCellRenderer dtcr = new AttributeTableCellRenderer(); 
 private AttributeView attributeView; 
 private static final int EXTRA_HEIGHT = 4
 private static final float TABLE_ROW_HEIGHT = 4
 private static final Dimension prefHeaderSize = new Dimension(18); 
 private static Logger mLogger = null
 
 AttributeTable(AttributeView attributeView) { 
  super(); 
  if (mLogger == null) { 
   mLogger = Resources.getInstance().getLogger( 
     this.getClass().getName()); 
  
  this.attributeView = attributeView; 
  addFocusListener(focusListener); 
  final MindMapNode model = attributeView.getNodeView().getModel(); 
  final AttributeController attributeController = model.getMap() 
    .getRegistry().getModeController().getAttributeController(); 
  if (attributeController != null) { 
   getTableHeader().addMouseListener(componentListener); 
  else { 
   getTableHeader().setResizingAllowed(false); 
  
  setModel(attributeView.getCurrentAttributeTableModel()); 
  updateFontSize(this1F); 
  updateColumnWidths(); 
  setAutoResizeMode(AUTO_RESIZE_OFF); 
  getTableHeader().setReorderingAllowed(false); 
  getTableHeader().setPreferredSize(prefHeaderSize); 
  int h = getRowHeight(); 
  updateRowHeights(); 
  setRowSelectionAllowed(false); 
  putClientProperty("JTable.autoStartsEdit", Boolean.FALSE); 
 
 
 public TableCellRenderer getCellRenderer(int row, int column) { 
  String text = getValueAt(row, column).toString(); 
  dtcr.setText(text); 
  int prefWidth = dtcr.getPreferredSize().width; 
  int width = getColumnModel().getColumn(column).getWidth(); 
  if (prefWidth > width) { 
   dtcr.setToolTipText(text); 
  else { 
   dtcr.setToolTipText(null); 
  
  return dtcr; 
 
 
 public TableCellEditor getCellEditor(int row, int column) { 
  JComboBox comboBox = new JComboBox(); 
  DefaultCellEditor dce = new DefaultCellEditor(comboBox); 
  return dce; 
 
 
 /**
     *  
     */
 
 public Component prepareEditor(TableCellEditor tce, int row, int col) { 
  ComboBoxModel model; 
  JComboBox comboBox = (JComboBox) ((DefaultCellEditor) tce) 
    .getComponent(); 
  MindMapNode node = getAttributeTableModel().getNode(); 
  AttributeRegistry attributes = node.getMap().getRegistry() 
    .getAttributes(); 
  switch (col) { 
  case 0
   model = attributes.getComboBoxModel(); 
   comboBox.setEditable(!attributes.isRestricted()); 
   break
  case 1
   String attrName = getAttributeTableModel().getValueAt(row, 0
     .toString(); 
   model = attributes.getDefaultComboBoxModel(attrName); 
   comboBox.setEditable(!attributes.isRestricted(attrName)); 
   break
  default
   model = getDefaultComboBoxModel(); 
  
  comboBox.setModel(model); 
  model.setSelectedItem(getValueAt(row, col)); 
 
  comboBox.addFocusListener(focusListener); 
  comboBox.getEditor().getEditorComponent() 
    .addFocusListener(focusListener); 
  Component editor = super.prepareEditor(tce, row, col); 
  updateFontSize(editor, getZoom()); 
  return editor; 
 
 
 public Dimension getPreferredScrollableViewportSize() { 
  if (!isValid()) 
   validate(); 
  float zoom = getZoom(); 
  Dimension dimension = super.getPreferredSize(); 
  dimension.width = Math.min((int) (MAX_WIDTH * zoom), dimension.width); 
  dimension.height = Math.min((int) (MAX_HEIGTH * zoom) 
    - getTableHeaderHeight(), dimension.height); 
  return dimension; 
 
 
 static ComboBoxModel getDefaultComboBoxModel() { 
  if (defaultComboBoxModel == null) { 
   defaultComboBoxModel = new DefaultComboBoxModel(); 
  
  return defaultComboBoxModel; 
 
 
 public void changeSelection(int rowIndex, int columnIndex, boolean toggle, 
   boolean extend) { 
  int rowCount = getRowCount(); 
  if (rowCount == 0
   return
  if (rowIndex >= rowCount) { 
   rowIndex = 0
   columnIndex = 0
  
  changeSelectedRowHeight(rowIndex); 
  super.changeSelection(rowIndex, columnIndex, toggle, extend); 
 
 
 private void changeSelectedRowHeight(int rowIndex) { 
  if (highRowIndex != rowIndex) { 
   if (highRowIndex < getRowCount()) { 
    int h = getRowHeight(highRowIndex); 
    setRowHeight(highRowIndex, h - EXTRA_HEIGHT); 
   
   int h = getRowHeight(rowIndex); 
   setRowHeight(rowIndex, h + EXTRA_HEIGHT); 
   highRowIndex = rowIndex; 
  
 
 
 /**
     *  
     */
 
 void updateAttributeTable() { 
  updateFontSize(this1F); 
  updateRowHeights(); 
  updateColumnWidths(); 
 
 
 private void updateColumnWidths() { 
  float zoom = getZoom(); 
  for (int i = 0; i < 2; i++) { 
   int width = (int) (getAttributeTableModel().getColumnWidth(i) * zoom); 
   getColumnModel().getColumn(i).setPreferredWidth(width); 
  
 
 
 private void updateRowHeights() { 
  int rowCount = getRowCount(); 
  if (rowCount == 0
   return
 
  int constHeight = getTableHeaderHeight() + EXTRA_HEIGHT; 
  float zoom = getZoom(); 
  float fontSize = getFontSize(); 
  float tableRowHeight = fontSize + zoom * TABLE_ROW_HEIGHT; 
  int newHeight = (int) ((tableRowHeight * rowCount + (zoom - 1
    * constHeight) / rowCount); 
  if (newHeight < 1) { 
   newHeight = 1
  
  int highRowsNumber = (int) ((tableRowHeight - newHeight) * rowCount); 
  for (int i = 0; i < highRowsNumber; i++) { 
   setRowHeight(i, 1 + newHeight 
     + (i == highRowIndex ? EXTRA_HEIGHT : 0)); 
  
  for (int i = highRowsNumber; i < rowCount; i++) { 
   setRowHeight(i, newHeight + (i == highRowIndex ? EXTRA_HEIGHT : 0)); 
  
 
 
 int getTableHeaderHeight() { 
  final JTableHeader tableHeader = getTableHeader(); 
  return tableHeader != null ? tableHeader.getPreferredSize().height : 0
 
 
 private void updateFontSize(Component c, float zoom) { 
  // 1) Determine font 
  Font font = c.getFont(); 
  if (font != null) { 
   float oldFontSize = font.getSize2D(); 
   float newFontSize = getFontSize() * zoom; 
   if (oldFontSize != newFontSize) { 
    font = font.deriveFont(newFontSize); 
    c.setFont(font); 
   
  
 
 
 float getZoom() { 
  return attributeView.getMapView().getZoom(); 
 
 
 public void tableChanged(TableModelEvent e) { 
  super.tableChanged(e); 
  if (getParent() == null
   return
 
  if (e.getType() == TableModelEvent.DELETE 
    && e.getFirstRow() == highRowIndex 
    && e.getFirstRow() == getRowCount() && e.getFirstRow() != 0) { 
   changeSelection(e.getFirstRow() - 10falsefalse); 
  else { 
   updateRowHeights(); 
  
 
  MapView map = getAttributeView().getNodeView().getMap(); 
  getParent().getParent().invalidate(); 
  map.getModel().nodeChanged(getAttributeView().getNode()); 
 
 
 public void viewRemoved() { 
  getModel().removeTableModelListener(this); 
 
 
 /*
  * (non-Javadoc) 
  *  
  * @see javax.swing.JTable#removeNotify() 
  */
 
 public void removeNotify() { 
  // TODO Auto-generated method stub 
  super.removeNotify(); 
 
 
 private float getFontSize() { 
  return (attributeView.getNodeView().getModel().getMap().getRegistry() 
    .getAttributes().getFontSize()); 
 
 
 public void setModel(TableModel dataModel) { 
  super.setModel(dataModel); 
 
 
 private void removeListenerFromEditor() { 
  JComboBox comboBox = (JComboBox) getEditorComponent(); 
  comboBox.removeFocusListener(focusListener); 
  comboBox.getEditor().getEditorComponent() 
    .removeFocusListener(focusListener); 
  comboBox.setModel(new DefaultComboBoxModel()); 
 
 
 public void removeEditor() { 
  removeListenerFromEditor(); 
  getAttributeTableModel().editingCanceled(); 
  super.removeEditor(); 
 
 
 /**
  * @return Returns the currentModel. 
  */
 
 public AttributeTableModelDecoratorAdapter getAttributeTableModel() { 
  return (AttributeTableModelDecoratorAdapter) getModel(); 
 
 
 public AttributeView getAttributeView() { 
  return attributeView; 
 
 
 /**
     *  
     */
 
 public void setOptimalColumnWidths() { 
  Component comp = null
  int cellWidth = 0
  int maxCellWidth = 2 * (int) (Math.ceil(getFontSize() 
    + TABLE_ROW_HEIGHT)); 
  for (int col = 0; col < 2; col++) { 
   for (int row = 0; row < getRowCount(); row++) { 
    comp = dtcr.getTableCellRendererComponent(this
      getValueAt(row, col), falsefalse, row, col); 
    cellWidth = comp.getPreferredSize().width; 
    maxCellWidth = Math.max(cellWidth, maxCellWidth); 
   
   getAttributeTableModel().setColumnWidth(col, maxCellWidth + 1); 
  
 
 
 /**     */ 
 public void insertRow(int row) { 
  if (getModel() instanceof ExtendedAttributeTableModelDecorator) { 
   ExtendedAttributeTableModelDecorator model = (ExtendedAttributeTableModelDecorator) getModel(); 
   if (isEditing() && getCellEditor() != null 
     && !getCellEditor().stopCellEditing()) { 
    return
   
   model.insertRow(row); 
   changeSelection(row, 0falsefalse); 
   if (editCellAt(row, 0)) 
    getEditorComponent().requestFocus(); 
  
 
 
 /**     */ 
 public void removeRow(int row) { 
  if (getModel() instanceof ExtendedAttributeTableModelDecorator) { 
   ExtendedAttributeTableModelDecorator model = (ExtendedAttributeTableModelDecorator) getModel(); 
   model.removeRow(row); 
  
 
 
 /**     */ 
 public void moveRowUp(int row) { 
  if (getModel() instanceof ExtendedAttributeTableModelDecorator) { 
   ExtendedAttributeTableModelDecorator model = (ExtendedAttributeTableModelDecorator) getModel(); 
   model.moveRowUp(row); 
  
 
 
 /**     */ 
 public void moveRowDown(int row) { 
  if (getModel() instanceof ExtendedAttributeTableModelDecorator) { 
   ExtendedAttributeTableModelDecorator model = (ExtendedAttributeTableModelDecorator) getModel(); 
   model.moveRowDown(row); 
  
 
 
 public void columnWidthChanged(ColumnWidthChangeEvent event) { 
  float zoom = getZoom(); 
  int col = event.getColumnNumber(); 
  AttributeTableLayoutModel layoutModel = (AttributeTableLayoutModel) event 
    .getSource(); 
  int width = layoutModel.getColumnWidth(col); 
  getColumnModel().getColumn(col).setPreferredWidth((int) (width * zoom)); 
  getAttributeView().getNode().getMap() 
    .nodeChanged(getAttributeView().getNode()); 
 
 
 protected boolean processKeyBinding(KeyStroke ks, KeyEvent e, 
   int condition, boolean pressed) { 
  mLogger.info("AttributeTable.processKeyBinding"); 
  if (ks.getKeyCode() == KeyEvent.VK_TAB && e.getModifiers() == 0 
    && pressed && getSelectedColumn() == 1 
    && getSelectedRow() == getRowCount() - 1 
    && getModel() instanceof ExtendedAttributeTableModelDecorator) { 
   insertRow(getRowCount()); 
   return true
  
  if (ks.getKeyCode() == KeyEvent.VK_ESCAPE && e.getModifiers() == 0 
    && pressed) { 
   attributeView.getNodeView().requestFocus(); 
   return true
  
  boolean retValue = super.processKeyBinding(ks, e, condition, pressed); 
  // Start editing when a key is typed. UI classes can disable this 
  // behavior 
  // by setting the client property JTable.autoStartsEdit to 
  // Boolean.FALSE. 
  if (!retValue 
    && condition == WHEN_FOCUSED 
    && isFocusOwner() 
    && ks.getKeyCode() != KeyEvent.VK_TAB 
    && e != null 
    && e.getID() == KeyEvent.KEY_PRESSED 
    && !e.isActionKey() 
    && e.getKeyChar() != KeyEvent.CHAR_UNDEFINED 
    && 0 == (e.getModifiers() & (KeyEvent.CTRL_MASK | KeyEvent.ALT_MASK))) { 
   // We do not have a binding for the event. 
   // Try to install the editor 
   int leadRow = getSelectionModel().getLeadSelectionIndex(); 
   int leadColumn = getColumnModel().getSelectionModel() 
     .getLeadSelectionIndex(); 
   if (leadRow != -1 && leadColumn != -1 && !isEditing()) { 
    if (!editCellAt(leadRow, leadColumn)) { 
     return false
    
   
   Component editorComponent = getEditorComponent(); 
   // If the editorComponent is a JComboBox, pass the event to it. 
   if (editorComponent instanceof JComboBox) { 
    JComboBox comboBox = (JComboBox) editorComponent; 
    if (comboBox.isEditable()) { 
     ComboBoxEditor editor = comboBox.getEditor(); 
     editor.selectAll(); // to enable overwrite 
     KeyEvent keyEv; 
     keyEv = new KeyEvent(editor.getEditorComponent(), 
       KeyEvent.KEY_TYPED, e.getWhen(), e.getModifiers(), 
       KeyEvent.VK_UNDEFINED, e.getKeyChar(), 
       KeyEvent.KEY_LOCATION_UNKNOWN); 
     retValue = SwingUtilities.processKeyBindings(keyEv); 
    else { 
     editorComponent.requestFocus(); 
     retValue = true
    
   
  
  if (ks.getKeyCode() == KeyEvent.VK_SPACE) { 
   return true
  
  return retValue; 
 
 
 public boolean isVisible() { 
  return attributeView.areAttributesVisible(); 
 
}