/* * @(#)ShortcutField.java 7/9/2002 * * Copyright 2002 - 2002 JIDE Software Inc. All rights reserved. */ package com.jidesoft.swing; import com.jidesoft.plaf.UIDefaultsLookup; import com.jidesoft.utils.SystemInfo; import javax.swing.*; import javax.swing.border.Border; import javax.swing.border.EtchedBorder; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import java.awt.*; import java.awt.event.*; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; /** * <code>LabeledTextField</code> is a combo component which includes text field and an optional JLabel in the front and * another optional AbstractButton at the end. */ public class LabeledTextField extends JPanel { protected JTextField _textField; protected JLabel _label; protected AbstractButton _button; protected String _labelText; protected Icon _icon; protected String _hintText; protected boolean _showHintTextWhenFocused = false; protected JLabel _hintLabel; protected PopupMenuCustomizer _customizer; protected KeyStroke _contextMenuKeyStroke; private DefaultOverlayable _hintOverlayable; /** * The PopupMenuCustomizer for the context menu when clicking on the label/icon before the text field. */ public static interface PopupMenuCustomizer { void customize(LabeledTextField field, JPopupMenu menu); } public LabeledTextField() { this(null, null); } public LabeledTextField(Icon icon) { this(icon, null); } public LabeledTextField(Icon icon, String labelText) { super(); _icon = icon; _labelText = labelText; initComponent(); } protected void initComponent() { _label = createLabel(); if (_label != null) { _label.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { } @Override public void mousePressed(MouseEvent e) { showContextMenu(); } @Override public void mouseReleased(MouseEvent e) { } }); } _button = createButton(); _textField = createTextField(); initLayout(_label, _textField, _button); setContextMenuKeyStroke(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, KeyEvent.ALT_DOWN_MASK)); registerContextMenuKeyStroke(getContextMenuKeyStroke()); updateUI(); } private void registerContextMenuKeyStroke(KeyStroke keyStroke) { if (keyStroke != null) { registerKeyboardAction(new ActionListener() { public void actionPerformed(ActionEvent e) { showContextMenu(); } }, keyStroke, JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); } } private void unregisterContextMenuKeyStroke(KeyStroke keyStroke) { if (keyStroke != null) unregisterKeyboardAction(keyStroke); } /** * Shows the context menu. */ protected void showContextMenu() { if (isEnabled()) { JPopupMenu menu = createContextMenu(); customizePopupMenu(menu); PopupMenuCustomizer customizer = getPopupMenuCustomizer(); if (customizer != null && menu != null) { customizer.customize(this, menu); } if (menu != null && menu.getComponentCount() > 0) { Point location = calculateContextMenuLocation(); JideSwingUtilities.showPopupMenu(menu, this, location.x, location.y); } } } /** * Calculates the locatioin of the context menu. * * @return the upper-left corner location. * @since 3.4.2 */ protected Point calculateContextMenuLocation() { Point location = _label.getLocation(); return new Point(location.x + (_label.getIcon() == null ? 1 : _label.getIcon().getIconWidth() / 2), location.y + _label.getHeight() + 1); } /** * Customizes the popup menu. * * @param menu the menu to customize * @since 3.4.1 */ protected void customizePopupMenu(JPopupMenu menu) { } /** * Setup the layout of the components. By default, we used a border layout with label first, field in the center and * button last. * * @param label the label * @param field the text field. * @param button the button */ protected void initLayout(final JLabel label, final JTextField field, final AbstractButton button) { setLayout(new BorderLayout(3, 3)); if (label != null) { add(label, BorderLayout.BEFORE_LINE_BEGINS); } _hintLabel = new JLabel(getHintText()); _hintLabel.setOpaque(false); Color foreground = UIDefaultsLookup.getColor("Label.disabledForeground"); if (foreground == null) { foreground = Color.GRAY; } _hintLabel.setForeground(foreground); _hintOverlayable = new DefaultOverlayable(field, _hintLabel, DefaultOverlayable.LEADING); _hintOverlayable.setOpaque(false); field.addFocusListener(new FocusListener() { public void focusLost(FocusEvent e) { adjustOverlay(field, _hintOverlayable); } public void focusGained(FocusEvent e) { adjustOverlay(field, _hintOverlayable); } }); field.getDocument().addDocumentListener(new DocumentListener() { public void insertUpdate(DocumentEvent e) { adjustOverlay(field, _hintOverlayable); } public void removeUpdate(DocumentEvent e) { adjustOverlay(field, _hintOverlayable); } public void changedUpdate(DocumentEvent e) { adjustOverlay(field, _hintOverlayable); } }); add(_hintOverlayable); if (button != null) { add(button, BorderLayout.AFTER_LINE_ENDS); } } /** * Checks if the hint text will still be shown when the text field has focus. By default, the hint text is only * shown when the text field doesn't have focus. * * @return true or false. * @since 3.3.6 */ public boolean isShowHintTextWhenFocused() { return _showHintTextWhenFocused; } /** * Sets the flag if the hint text will still be shown when the text field has focus. By default, the hint text is * only shown when the text field doesn't have focus. If you set it to true, the hint text will always be shown * regardless if the text field has focus. * * @param showHintTextWhenFocused true or false. * @since 3.3.6 */ public void setShowHintTextWhenFocused(boolean showHintTextWhenFocused) { _showHintTextWhenFocused = showHintTextWhenFocused; if (_textField != null && _hintOverlayable != null) { adjustOverlay(_textField, _hintOverlayable); } } private void adjustOverlay(JTextField field, Overlayable overlayable) { if (field.hasFocus() && !isShowHintTextWhenFocused()) { overlayable.setOverlayVisible(false); } else { String text = field.getText(); if (text != null && text.length() != 0) { overlayable.setOverlayVisible(false); } else { overlayable.setOverlayVisible(true); } } } /** * Creates a text field. By default it will return a JTextField with opaque set to false. Subclass can override this * method to create their own text field such as JFormattedTextField. * * @return a text field. */ protected JTextField createTextField() { JTextField textField = new OverlayTextField(); SelectAllUtils.install(textField); JideSwingUtilities.setComponentTransparent(textField); textField.setColumns(20); return textField; } /** * Creates a context menu. The context menu will be shown when user clicks on the label. * * @return a context menu. */ protected JidePopupMenu createContextMenu() { return new JidePopupMenu(); } @Override public void updateUI() { super.updateUI(); Border textFieldBorder = UIDefaultsLookup.getBorder("TextField.border"); if (textFieldBorder != null) { boolean big = textFieldBorder.getBorderInsets(this).top >= 2; if (big) setBorder(textFieldBorder); else setBorder(BorderFactory.createCompoundBorder(textFieldBorder, BorderFactory.createEmptyBorder(2, 2, 2, 2))); } else { setBorder(BorderFactory.createCompoundBorder(BorderFactory.createEtchedBorder(EtchedBorder.LOWERED), BorderFactory.createEmptyBorder(2, 2, 2, 2))); } if (isEnabled()) { LookAndFeel.installColors(this, "TextField.background", "TextField.foreground"); } else { LookAndFeel.installColors(this, "TextField.disableBackground", "TextField.inactiveForeground"); } if (textFieldBorder != null && _textField != null) { _textField.setBorder(BorderFactory.createEmptyBorder()); } setEnabled(isEnabled()); } /** * Creates the button that appears after the text field. By default it returns null so there is no button. Subclass * can override it to create their own button. A typical usage of this is to create a browse button to browse a file * or directory. * * @return the button. */ protected AbstractButton createButton() { return null; } /** * Creates the label that appears before the text field. By default, it only has a search icon. * * @return the label. */ protected JLabel createLabel() { JLabel label = new JLabel(_icon); label.setText(_labelText); return label; } /** * Sets the text that appears before the text field. * * @param text the text that appears before the text field. */ public void setLabelText(String text) { _labelText = text; if (_label != null) { _label.setText(text); } } /** * Gets the text that appears before the text field. * * @return the text that appears before the text field. By default it's null, meaning no text. */ public String getLabelText() { if (_label != null) { return _label.getText(); } else { return _labelText; } } /** * Sets the icon that appears before the text field. * * @param icon the icon that appears before the text field. */ public void setIcon(Icon icon) { _icon = icon; if (_label != null) { _label.setIcon(icon); } } /** * Gets the icon that appears before the text field. * * @return the icon that appears before the text field. */ public Icon getIcon() { if (_label != null) { return _label.getIcon(); } else { return _icon; } } /** * Gets the JLabel that appears before text field. * * @return the JLabel that appears before text field. */ public JLabel getLabel() { return _label; } /** * Gets the AbstractButton that appears after text field. * * @return the AbstractButton that appears after text field. */ public AbstractButton getButton() { return _button; } /** * Sets the number of columns in this TextField, and then invalidate the layout. * * @param columns the number of columns for this text field. */ public void setColumns(int columns) { if (getTextField() != null) { getTextField().setColumns(columns); } } /** * Sets the text in this TextField. * * @param text the new text in this TextField. */ public void setText(String text) { if (getTextField() != null) { getTextField().setText(text); } } /** * Gets the text in this TextField. * * @return the text in this TextField. */ public String getText() { if (getTextField() != null) { return getTextField().getText(); } else { return null; } } /** * Gets the actual text field. * * @return the actual text field. */ public JTextField getTextField() { return _textField; } @Override public void setEnabled(boolean enabled) { super.setEnabled(enabled); if (enabled) { if (getTextField() != null) { getTextField().setEnabled(true); } if (getLabel() != null) { getLabel().setEnabled(true); } if (getButton() != null) { getButton().setEnabled(true); } } else { if (getTextField() != null) { getTextField().setEnabled(false); } if (getLabel() != null) { getLabel().setEnabled(false); } if (getButton() != null) { getButton().setEnabled(false); } setBackground(UIDefaultsLookup.getColor("control")); } if (_hintLabel != null) { boolean textEmpty = true; if (getTextField() != null) { textEmpty = getTextField().getText() == null || getTextField().getText().length() == 0; } _hintLabel.setVisible(isEnabled() && textEmpty); } JTextField textField = getTextField(); if (textField != null) { // this probably won't work with L&F which ignore the background property like GTK L&F setBackground(textField.getBackground()); setForeground(textField.getForeground()); } else { if (enabled) { setBackground(UIDefaultsLookup.getColor("TextField.background")); setForeground(UIDefaultsLookup.getColor("TextField.foreground")); } else { Color background = UIDefaultsLookup.getColor("TextField.disabledBackground"); if (background == null) { // TextField.disabledBackground not defined by metal background = UIDefaultsLookup.getColor("TextField.inactiveBackground"); // Nimbus uses TextField[Disabled].backgroundPainter (not a Color) // but don't know how to set that for a single panel instance, maybe with a ClientProperty? } setBackground(background); setForeground(UIDefaultsLookup.getColor("TextField.inactiveForeground")); } } } public int getBaseline(int width, int height) { if (SystemInfo.isJdk6Above()) { try { Method method = Component.class.getMethod("getBaseline", new Class[]{int.class, int.class}); Object value = method.invoke(_textField, width, height); if (value instanceof Integer) { return (Integer) value; } } catch (NoSuchMethodException e) { // ignore } catch (IllegalAccessException e) { // ignore } catch (InvocationTargetException e) { // ignore } } return -1; } /** * Gets the hint text when the field is empty and not focused. * * @return the hint text. */ public String getHintText() { return _hintText; } /** * Sets the hint text. * * @param hintText the new hint text. */ public void setHintText(String hintText) { _hintText = hintText; if (_hintLabel != null) { _hintLabel.setText(_hintText); } } /** * Gets the PopupMenuCustomizer. * * @return the PopupMenuCustomizer. */ public PopupMenuCustomizer getPopupMenuCustomizer() { return _customizer; } /** * Sets the PopupMenuCustomizer. PopupMenuCustomizer can be used to do customize the popup menu for the * <code>LabeledTextField</code>. * <p/> * PopupMenuCustomizer has a customize method. The popup menu of this menu will be passed in. You can * add/remove/change the menu items in customize method. For example, * <code><pre> * field.setPopupMenuCustomizer(new LabeledTextField.PopupMenuCustomizer() { * void customize(LabeledTextField field, JPopupMenu menu) { * menu.removeAll(); * menu.add(new JMenuItem("...")); * menu.add(new JMenuItem("...")); * } * } * </pre></code> * If the menu is never used, the two add methods will never be called thus improve the performance. * * @param customizer the PopupMenuCustomizer */ public void setPopupMenuCustomizer(PopupMenuCustomizer customizer) { _customizer = customizer; } /** * Gets the keystroke that will bring up the context menu. If you never set it before, it will return SHIFT-F10 for * operating systems other than Mac OS X. * * @return the keystroke that will bring up the context menu. */ public KeyStroke getContextMenuKeyStroke() { if (_contextMenuKeyStroke == null) { _contextMenuKeyStroke = !SystemInfo.isMacOSX() ? KeyStroke.getKeyStroke(KeyEvent.VK_F10, KeyEvent.SHIFT_MASK) : null; } return _contextMenuKeyStroke; } /** * Changes the keystroke that brings up the context menu which is normally shown when user clicks on the label icon * before the text field. * * @param contextMenuKeyStroke the new keystroke to bring up the context menu. */ public void setContextMenuKeyStroke(KeyStroke contextMenuKeyStroke) { if (_contextMenuKeyStroke != null) { unregisterContextMenuKeyStroke(_contextMenuKeyStroke); } _contextMenuKeyStroke = contextMenuKeyStroke; if (_contextMenuKeyStroke != null) { registerContextMenuKeyStroke(_contextMenuKeyStroke); } } }