/*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); } } } } }); } /* * (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(1, 8); 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(this, 1F); 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(this, 1F); 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() - 1, 0, false, false); } 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), false, false, 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, 0, false, false); 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(); } }