/* * Copyright (c) 2007 BUSINESS OBJECTS SOFTWARE LIMITED * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of Business Objects nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /* * StringValueEditor.java * Creation date: (02/03/01 10:20:15 AM) * By: Michael Cheng */ package org.openquark.gems.client.valueentry; import java.awt.Adjustable; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Cursor; import java.awt.Dimension; import java.awt.Insets; import java.awt.event.KeyEvent; import javax.swing.JScrollBar; import javax.swing.JScrollPane; import javax.swing.JTextArea; import javax.swing.ScrollPaneConstants; import javax.swing.SwingUtilities; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import org.openquark.cal.valuenode.ListOfCharValueNode; import org.openquark.cal.valuenode.ListValueNode; import org.openquark.cal.valuenode.LiteralValueNode; import org.openquark.cal.valuenode.ValueNode; /** * The ValueEditor used to edit the String type and the [Char] type. * Creation date: (02/03/01 10:20:15 AM) * @author Michael Cheng */ class StringValueEditor extends ValueEditor { private static final long serialVersionUID = 6253045166403650749L; /** * A custom value editor provider for the StringValueEditor. */ public static class StringValueEditorProvider extends ValueEditorProvider<StringValueEditor> { public StringValueEditorProvider(ValueEditorManager valueEditorManager) { super(valueEditorManager); } /** * {@inheritDoc} */ @Override public boolean canHandleValue(ValueNode valueNode, SupportInfo providerSupportInfo) { return valueNode instanceof ListOfCharValueNode || (valueNode instanceof LiteralValueNode && valueNode.getTypeExpr().sameType(getValueEditorManager().getValueNodeBuilderHelper().getPreludeTypeConstants().getStringType())); } /** * @see org.openquark.gems.client.valueentry.ValueEditorProvider#getEditorInstance(ValueEditorHierarchyManager, ValueNode) */ @Override public StringValueEditor getEditorInstance(ValueEditorHierarchyManager valueEditorHierarchyManager, ValueNode valueNode) { StringValueEditor editor = new StringValueEditor(valueEditorHierarchyManager); editor.setOwnerValueNode(valueNode); return editor; } /** * {@inheritDoc} */ @Override public boolean usableForOutput() { return true; } } /** * Very similar to ValueEditorKeyListener, with the exception that * Alt + Enter is required for a commit, instead of just Enter. */ public class StringValueEditorKeyListener extends ValueEditorKeyListener { @Override public void keyPressed(KeyEvent evt) { if ((evt.getKeyCode() == KeyEvent.VK_ENTER) && evt.isAltDown()) { handleCommitGesture(); evt.consume(); // Don't want the control with the focus to perform its action. } else if (evt.getKeyCode() == KeyEvent.VK_ESCAPE) { handleCancelGesture(); evt.consume(); } } } /** * This listener grows the text field up to a maximum size * if the user enters additional text. */ private class StringValueEditorDocumentListener implements DocumentListener { public void insertUpdate (DocumentEvent e) { // We can't resize the text are while the document insert hasn't completed. // Doing the resize at this time can cause problems with text layout for the area. // Therefore we invoke the resize once AWT events have finished processing. SwingUtilities.invokeLater(new Runnable() { public void run() { resetSize(); } }); } public void changedUpdate (DocumentEvent e) { } public void removeUpdate (DocumentEvent e) { } } /** * This constant is the maximum number of lines the editor will * display by default when first being shown and is also the maximum * number of lines it will grow to when the user enters text. */ public static final int DEFAULT_MAXIMUM_LINES = 40; /** The default maximum width in pixels the editor will display or grow to. */ public static final int DEFAULT_MAXIMUM_WIDTH = 600; /** True if the user manually resizes this editor. */ private boolean userHasResized = false; /** The scroll pane that contains the text area. */ private JScrollPane ivjJScrollPane1 = null; /** The text area for editing the text. */ private JTextArea ivjTextArea = null; /** * StringValueEditor constructor comment. * @param valueEditorHierarchyManager */ protected StringValueEditor(ValueEditorHierarchyManager valueEditorHierarchyManager) { super(valueEditorHierarchyManager); initialize(); } /** * {@inheritDoc} */ @Override protected void commitValue() { String stringValue = getTextArea().getText(); ValueNode valueNode = getValueNode(); if (valueNode instanceof ListOfCharValueNode || valueNode instanceof ListValueNode) { // TEMP (?): Lists of Chars are collapsed to ListOfChars ValueNode returnVN = new ListOfCharValueNode(stringValue, getValueNode().getTypeExpr()); replaceValueNode(returnVN, true); } else if (valueNode instanceof LiteralValueNode){ ValueNode returnVN = new LiteralValueNode(stringValue, getValueNode().getTypeExpr()); replaceValueNode(returnVN, true); } else { throw new IllegalStateException("This value node cannot be handled by the string editor: " + valueNode.getClass()); } valueEditorManager.associateInfo(getOwnerValueNode(), new ValueEditor.Info(getSize())); notifyValueCommitted(); } /** * {@inheritDoc} */ @Override public Component getDefaultFocusComponent() { return getTextArea(); } /** * Return the JScrollPane1 property value. * @return JScrollPane */ private JScrollPane getScrollPane() { if (ivjJScrollPane1 == null) { try { ivjJScrollPane1 = new JScrollPane(); ivjJScrollPane1.setName("ScrollPane"); ivjJScrollPane1.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED); ivjJScrollPane1.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED); getScrollPane().setViewportView(getTextArea()); } catch (Throwable ivjExc) { handleException(ivjExc); } } return ivjJScrollPane1; } /** * Return the TextArea property value. * @return JTextArea */ private JTextArea getTextArea() { if (ivjTextArea == null) { try { ivjTextArea = new JTextArea(); ivjTextArea.setName("TextArea"); ivjTextArea.setLineWrap(false); ivjTextArea.setWrapStyleWord(true); ivjTextArea.setBounds(0, 0, 160, 120); } catch (Throwable ivjExc) { handleException(ivjExc); } } return ivjTextArea; } /** * Called whenever the part throws an exception. * @param exception Throwable */ private void handleException(Throwable exception) { /* Uncomment the following lines to print uncaught exceptions to stdout */ System.out.println("--------- UNCAUGHT EXCEPTION ---------"); exception.printStackTrace(System.out); } /** * Initialize the class. * Note: Extra Set-up Code has been added. */ private void initialize() { try { setName("StringValueEditor"); setLayout(new BorderLayout()); add(getScrollPane(), "Center"); } catch (Throwable ivjExc) { handleException(ivjExc); } // Make sure that this StringValueEditor handles user's commit or cancel input. getTextArea().addKeyListener(new StringValueEditorKeyListener()); // Set default cursor for components that should have only ever have default cursors // (Mouse pointer changes near the edge of the border. getScrollPane().setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); } /** * @see org.openquark.gems.client.valueentry.ValueEditor#setInitialValue() */ @Override public void setInitialValue() { String stringVal = null; if (getValueNode() instanceof ListOfCharValueNode) { stringVal = ((ListOfCharValueNode)getValueNode()).getStringValue(); } else if (getValueNode() instanceof LiteralValueNode) { stringVal = ((LiteralValueNode)getValueNode()).getStringValue(); } else { stringVal = getValueNode().getTextValue(); } getTextArea().setText(stringVal); getTextArea().setCaretPosition(0); resetSize(); setResizable(true); getTextArea().getDocument().addDocumentListener(new StringValueEditorDocumentListener ()); } /** * @see org.openquark.gems.client.valueentry.ValueEditor#userHasResized() */ @Override protected void userHasResized() { // If the user does resize the editor we want to enable line wrapping. getTextArea().setLineWrap(true); userHasResized = true; } /** * Sets the size of the editor depending on the preferred size of the text area. */ private void resetSize () { boolean isEditable = isEditable(); int lineHeight = getTextArea().getFontMetrics(getTextArea().getFont()).getHeight(); Dimension borders = getBorderSize(); Dimension bestSize = getTextArea().getPreferredSize(); // Always set a reasonable minimum size. setMinResizeDimension(new Dimension (100, lineHeight + borders.height)); // Don't resize the editor if the user manually picked a size they like. if (userHasResized) { return; } // Restore the saved size if there is any. ValueEditor.Info info = valueEditorManager.getInfo(getOwnerValueNode()); if (info != null) { userHasResized = true; getTextArea().setLineWrap(true); setSize(info.getEditorSize()); return; } bestSize.height += borders.height; bestSize.width += borders.width; // Give the text area a dummy default size. Do this so that the BasicTextUI // will return a sensible value for the preferred size of the text area. getTextArea().setSize(DEFAULT_MAXIMUM_WIDTH, DEFAULT_MAXIMUM_LINES * lineHeight); if (isEditable) { setMaxResizeDimension(null); } else { setMaxResizeDimension(bestSize); } // Compare the new sizes with the current size // If they are bigger then we want to grow the editor Dimension currentSize = getSize(); int newHeight = getSize().height; int newWidth = getSize().width; if (!isEditable || bestSize.height > currentSize.height) { newHeight = Math.min (bestSize.height, DEFAULT_MAXIMUM_LINES * lineHeight); } if (!isEditable || bestSize.width > currentSize.width) { newWidth = Math.min (bestSize.width, DEFAULT_MAXIMUM_WIDTH); } if (isEditable) { // Make sure the editor has a reasonable size if it is editable and the text is small newHeight = Math.max(newHeight, lineHeight * 10); newWidth = Math.max(newWidth, 300); } // If there are lines longer than the maximum size we grow to, then enable line // wrapping. This causes the preferred size of the text area to change, so we // have to reset the size again after we do this. if (bestSize.width > DEFAULT_MAXIMUM_WIDTH && !getTextArea().getLineWrap()) { getTextArea().setLineWrap(true); resetSize(); return; } // Pick the new size so that we don't accidentally go to a smaller // size than we are now. Sometimes the new size ca be smaller if the // user erases characters and enters new characters that are narrower. setSize (new Dimension (Math.max (newWidth, currentSize.width), Math.max (newHeight, currentSize.height))); revalidate(); } /** * Calculates the total dimension of all borders around the text area of * the value editor. The size for the value editor should include the size * intended for the text area plus the size of the total border. * * @return a dimension with the total border height and width */ private Dimension getBorderSize() { // The border of the scrollpane. Insets insets = getScrollPane().getInsets(); int borderHeight = insets.top + insets.bottom; int borderWidth = insets.left + insets.right; // The border of the value editor itself. insets = getInsets(); borderHeight += insets.top + insets.bottom; borderWidth += insets.left + insets.right; // The size of the scrollbars. JScrollBar scrollbar = new JScrollBar(Adjustable.HORIZONTAL); borderHeight += scrollbar.getPreferredSize().height; scrollbar = new JScrollBar(Adjustable.VERTICAL); borderWidth += scrollbar.getPreferredSize().width; // Add 5 pixels to the width for good looks borderWidth += 5; return new Dimension (borderWidth, borderHeight); } /** * Set the editable status of this StringValueEditor, and make any * necessary internal adjustments. */ @Override public void setEditable(boolean editable) { super.setEditable(editable); getTextArea().setEditable(editable); getTextArea().setForeground(editable ? Color.black : Color.gray); } }