/* Copyright (c) 2006-2007 Timothy Wall, All Rights Reserved * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * <p/> * This library 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 * Lesser General Public License for more details. */ package furbelow; import javax.swing.*; import javax.swing.Timer; import javax.swing.event.*; import javax.swing.text.DefaultEditorKit; import java.awt.*; import java.awt.datatransfer.*; import java.awt.event.*; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.IOException; import java.text.MessageFormat; import java.util.ArrayList; import java.util.List; /** Demonstration of context-sensitive menu items based on {@link Action}s * exported by a {@link JComponent}. * @author twall */ public class DelegatingAction extends AbstractAction implements PropertyChangeListener { private class FocusChangeListener implements PropertyChangeListener { public void propertyChange(PropertyChangeEvent e) { Component c = (Component)e.getNewValue(); if (c instanceof JComponent) { ActionMap map = ((JComponent)c).getActionMap(); Action a = map.get(actionKey); if (a == null) { a = map.get(actionKey2); } setDelegate(a); target = c; } else { setDelegate(null); target = null; } } } public static class DisabledAction extends AbstractAction { public DisabledAction(String name) { super(name); setEnabled(false); } public void actionPerformed(ActionEvent e) { } } private static class ListSelectionDimmer { public ListSelectionDimmer(final JList list) { KeyboardFocusManager fm = KeyboardFocusManager.getCurrentKeyboardFocusManager(); fm.addPropertyChangeListener("permanentFocusOwner", new PropertyChangeListener() { private Color oldColor; public void propertyChange(PropertyChangeEvent e) { if (e.getOldValue() == list) { if (oldColor == null) { oldColor = list.getSelectionBackground(); Color bg = list.getBackground(); bg = new Color((bg.getRed() + oldColor.getRed())/2, (bg.getGreen() + oldColor.getGreen())/2, (bg.getBlue() + oldColor.getBlue())/2); list.setSelectionBackground(bg); } } else if (e.getNewValue() == list) { if (oldColor != null) { list.setSelectionBackground(oldColor); oldColor = null; } } } }); } } private Action delegate; private Component target; private String actionKey; private String actionKey2; public DelegatingAction(String name, String key, String altKey) { super(name); actionKey = key; actionKey2 = altKey; KeyboardFocusManager fm = KeyboardFocusManager.getCurrentKeyboardFocusManager(); fm.addPropertyChangeListener("permanentFocusOwner", new FocusChangeListener()); } /** Enables and delegates to the target action only if the clipboard * has contents. */ public static abstract class PasteAction extends AbstractAction { public PasteAction() { super("paste"); KeyboardFocusManager fm = KeyboardFocusManager.getCurrentKeyboardFocusManager(); fm.addPropertyChangeListener("permanentFocusOwner", new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent e) { setEnabled(clipboardHasContents()); } }); } } protected static String getClipboardContents() { try { Clipboard clip = Toolkit.getDefaultToolkit().getSystemClipboard(); Transferable t = clip.getContents(null); return (String)t.getTransferData(DataFlavor.stringFlavor); } catch(UnsupportedFlavorException ex) { return null; } catch (IOException e) { return null; } } protected static boolean clipboardHasContents() { String contents = getClipboardContents(); return contents != null && !"".equals(contents); } private void setDelegate(Action delegate) { if (this.delegate != null) { this.delegate.removePropertyChangeListener(this); } this.delegate = delegate; if (delegate != null) { delegate.addPropertyChangeListener(this); } setEnabled(delegate != null && delegate.isEnabled()); } public void propertyChange(PropertyChangeEvent e) { if ("enabled".equals(e.getPropertyName())) { setEnabled(Boolean.TRUE.equals(e.getNewValue())); } } public void actionPerformed(ActionEvent e) { if (delegate != null) { // Have to keep track of the target since TransferHandler // actions use a single, shared Action instance if (target != null) { e = new ActionEvent(target, e.getID(), e.getActionCommand(), e.getWhen(), e.getModifiers()); } delegate.actionPerformed(e); } } private static JList createList(boolean readOnly) { final Object[] data = { "Apples", "Oranges", "Bananas", "Kiwis", "Turnips", "Rutabagas", }; final JList list = new JList(data); new ListSelectionDimmer(list); final ActionMap map = list.getActionMap(); if (readOnly) { // Have to override these to avoid having Swing automatically // delegate to an installed TransferHandler map.put("cut", new DisabledAction("cut")); map.put("paste", new DisabledAction("paste")); } else { map.put("cut", new AbstractAction("cut") { public void actionPerformed(ActionEvent e) { map.get("copy").actionPerformed(e); final List contents = new ArrayList(); ListModel model = list.getModel(); int[] indices = list.getSelectedIndices(); for (int i=0;i < model.getSize();i++) { if (!list.isSelectedIndex(i)) { contents.add(model.getElementAt(i)); } } list.setModel(new AbstractListModel() { public int getSize() { return contents.size(); } public Object getElementAt(int index) { return contents.get(index); } }); if (indices.length == 1) { if (indices[0] < contents.size()) { list.setSelectedIndex(indices[0]); } else if (indices[0] == contents.size()) { list.setSelectedIndex(contents.size()-1); } } } }); // Only enable "paste" if the clipboard is non-empty map.put("paste", new PasteAction() { public void actionPerformed(ActionEvent e) { final List contents = new ArrayList(); ListModel model = list.getModel(); for (int i=0;i < model.getSize();i++) { contents.add(model.getElementAt(i)); } int idx = list.getSelectedIndex(); if (idx == -1 && model.getSize() == 0) { idx = 0; } String s = getClipboardContents(); if (idx != -1 && s != null) { contents.add(idx, s); list.setModel(new AbstractListModel() { public int getSize() { return contents.size(); } public Object getElementAt(int index) { return contents.get(index); } }); list.setSelectedIndex(idx); } } }); } return list; } public static void main(String[] args) { JFrame frame = new JFrame("Delegating Edit Menu"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); JMenuBar mb = new JMenuBar(); JMenu menu = new JMenu("Edit"); menu.add(new JMenuItem(new DelegatingAction("Cut", "cut", DefaultEditorKit.cutAction))); menu.add(new JMenuItem(new DelegatingAction("Copy", "copy", DefaultEditorKit.copyAction))); menu.add(new JMenuItem(new DelegatingAction("Paste", "paste", DefaultEditorKit.pasteAction))); mb.add(menu); frame.setJMenuBar(mb); JTextField textField = new JTextField("Select this text and copy"); final MessageFormat fmt = new MessageFormat("<html><i>Clipboard Contents:</i><br>{0}</html>"); final JLabel label = new JLabel(fmt.format(new Object[] {"<br>"})); JPanel p = (JPanel)frame.getContentPane(); JPanel split = new JPanel(new GridLayout(0, 2)); JPanel pleft = new JPanel(new BorderLayout()); pleft.add(new JLabel("Copy only"), BorderLayout.NORTH); pleft.add(new JScrollPane(createList(true))); split.add(pleft); JPanel pright = new JPanel(new BorderLayout()); pright.add(new JLabel("Copy, Cut, or Paste non-empty text"), BorderLayout.NORTH); pright.add(new JScrollPane(createList(false))); split.add(pright); p.add(split, BorderLayout.CENTER); p.add(textField, BorderLayout.PAGE_START); p.add(label, BorderLayout.PAGE_END); // Display what's currently on the clipboard Timer timer = new Timer(500, new ActionListener() { public void actionPerformed(ActionEvent e) { String s = getClipboardContents(); if (s == null) s = "{empty or not a String}"; final int MAX = 200; if (s.length() > MAX) { s = s.substring(0, MAX-3) + "..."; } label.setText(fmt.format(new Object[] { s })); } }); timer.start(); timer.setRepeats(true); frame.pack(); frame.setSize(400, 300); frame.setLocation(100, 100); frame.setVisible(true); } }