/* * Sun Public License Notice * * The contents of this file are subject to the Sun Public License * Version 1.0 (the "License"). You may not use this file except in * compliance with the License. A copy of the License is available at * http://www.sun.com/ * * The Original Code is NetBeans. The Initial Developer of the Original * Code is Sun Microsystems, Inc. Portions Copyright 1997-2003 Sun * Microsystems, Inc. All Rights Reserved. */ package org.openide.awt; import java.awt.*; import java.awt.event.*; import java.io.*; import java.util.List; import javax.swing.*; import javax.swing.event.*; /** * Menu element that can contain other menu items. These items are then * displayed "inline". The JInlineMenu can be used to componse more menu items * into one that can be added/removed at once. * * @deprecated JInlineMenu is resizing itself after being displayed, this * behavior causes a lot of troubles for Swing/AWT on various platforms. Very * hard to be fixed. Module developers should stop using this class. * * @author Jan Jancura */ public class JInlineMenu extends JMenuItem { /** generated Serialized Version UID */ static final long serialVersionUID = -2310488127953523571L; /** north separator */ private JSeparator north = new JSeparator (); /** south separator */ private JSeparator south = new JSeparator (); /** Stores inner MenuItems added to outer menu. */ private JComponent[] items = new JComponent[0]; /** true iff items of this menu are up to date */ boolean upToDate; /** private List of the items previously added to the parent menu */ private List addedItems; /** * Creates new JInlineMenu. */ public JInlineMenu () { setEnabled (false); upToDate = true; } /** Overriden to eliminate big gap at top of JInline popup painting. * @return cleared instets (0, 0, 0, 0) */ public Insets getInsets() { return new Insets(0, 0, 0, 0); } /** * Setter for array of items to display. Can be called only from event queue * thread. * * @param newItems array of menu items to display */ public void setMenuItems (final JMenuItem[] newItems) { // if(!SwingUtilities.isEventDispatchThread()) { //System.err.println("JInlineMenu.setMenuItems called outside of event queue !!!"); //Thread.dumpStack(); // } // make a tuned private copy JComponent[] local = new JComponent[newItems.length]; for(int i = 0; i < newItems.length; i++) { local[i] = newItems[i] != null ? (JComponent)newItems[i] : new JSeparator(); } items = local; upToDate = false; // tell the parent it is not up-to-date as well Container parent = getParent(); while (parent instanceof JInlineMenu) { ((JInlineMenu)parent).upToDate = false; parent = parent.getParent(); } if (isShowing()) { // Ugly thing have happened - we're already visible //System.err.println("JInlineMenu.setMenuItems on visible is deprecated !!!"); //Thread.dumpStack(); SwingUtilities.invokeLater(new Updater()); } } /* This method is overriden so that this class now allow following * pattern to be used: * * (1) nm = new JInlineMenu(); * (2) nm.setMenuItems( ... some items ... ); * (3) myJPopupMenu.add(nm); * * While the old source required (1) (3) and (2) */ public void addNotify() { // addNotify it quite late to do anything, but we'll try super.addNotify(); if (!upToDate) { //System.err.println("InvokeLater-ing from addNotify()"); SwingUtilities.invokeLater(new Updater()); } } static void prepareItemsInContainer(Container c) { Component[] comps = c.getComponents(); for (int i=0; i<comps.length; i++) { if (comps[i] instanceof JInlineMenu) { ((JInlineMenu)comps[i]).doUpdate(); } } } private void doUpdate() { // Let the subclasses add their own update logic, done this // way because we want to go w/o API change for 3.4 on this topic // when we're going to _solve_ this for 4.0 Object prop = getClientProperty("hack.preShowUpdater"); if (prop instanceof Runnable) ((Runnable)prop).run(); updateContents(); } /** This method is called only when in AWT, never in addNotify * so it is safe to operate with the parent menu content */ private void updateContents() { Container parent = getParent(); if (!upToDate && parent != null) { if(! (parent instanceof JInlineMenu)) { // we're the highest JInlineMenu, do the update ourself //call around all our subitems to prepare them for (int i=0; i<items.length; i++) { Object prop = items[i].getClientProperty("hack.preShowUpdater"); if (prop instanceof Runnable) { ((Runnable)prop).run(); } } removeItems(); addItems(); } upToDate = true; // we've been processed } } /** Remove all current items. */ private void removeItems () { JComponent m = (JComponent) getParent (); if (m == null) return; // Can't happen if(m instanceof JInlineMenu) { // Delegate removing to parent JInlineMenu. ((JInlineMenu)m).removeItems(); return; } // Remove all the items we've previously added. if (addedItems != null) { java.util.Iterator it = addedItems.iterator(); while (it.hasNext()) m.remove((Component)it.next()); } // Remove also our separators m.remove (north); m.remove (south); addedItems = null; } /** Gets all inline items including inline items from * sub-<code>JInlineMenu</code>'s. Used only by parent * <code>JInlineMenu</code>. */ private void getAllInlineItems(List its) { for(int i = 0; i < items.length; i++) { Object item = items[i]; if(item instanceof JInlineMenu) { ((JInlineMenu)item).getAllInlineItems(its); } else { its.add(item); } } } /** Finds the index of a component in array of components. * @return index or -1 */ private static int findIndex (Object of, Object[] arr) { int menuLength = arr.length; for (int i = 0; i < menuLength; i++) { if (of == arr[i]) { return i; } } return -1; } void addItems () { JComponent m = (JComponent) getParent (); if (m == null) return; // Can't happen boolean usedToBeContained = false; if (m instanceof JPopupMenu) { usedToBeContained = JPopupMenuUtils.isPopupContained ((JPopupMenu) m); } // Get all items, including those ones from sub-JInlineMenu's. List its = new java.util.ArrayList(items.length); getAllInlineItems(its); JComponent[] items = (JComponent[])its.toArray(new JComponent[its.size()]); addedItems = its; // Find me please! Component[] array = m.getComponents (); int menuPos = findIndex (this, array); if (menuPos == -1) return; // not found? strange! if ( menuPos > 0 && array.length > 0 && /* should be always true */ !(array[menuPos - 1] instanceof JSeparator) && !(array[menuPos - 1] instanceof JInlineMenu) ) { // not first and not after separator or another inline menu ==>> add separator before m.add (north, menuPos++); array = m.getComponents (); } if (menuPos < array.length - 1) { // not last if ( items.length > 0 && !(array[menuPos + 1] instanceof JPopupMenu.Separator) && !(array[menuPos + 1] instanceof JSeparator) ) { // adding non-zero items and not before separator m.add (south, menuPos + 1); } else if ( items.length == 0 && (array[menuPos + 1] instanceof JPopupMenu.Separator || array[menuPos + 1] instanceof JSeparator) ) { // adding zero items and there is an extra separator after the JInlineMenu item ==>> remove it m.remove (menuPos + 1); array = m.getComponents(); } } // Add components to outer menu. if (menuPos > array.length) { int menuLength = items.length; for (int i = 0; i < menuLength; i++) { m.add (items[i]); } } else { int menuLength = items.length; for (int i = 0; i < menuLength; i++) { m.add (items[i], ++menuPos); // advance menuPos for JInlineMenu // otherwise the next item will be // actually placed before expanded // items of this JInlineMenu if (items[i] instanceof JInlineMenu) { JInlineMenu him = (JInlineMenu) items[i]; menuPos += him.items.length; } } } if(m instanceof JPopupMenu && m.isShowing()) { // This can happen when somebody call setMenuItems on visible JPopupMenuUtils.dynamicChange((JPopupMenu)m, usedToBeContained); } else { // ensure correct preferred size computation m.invalidate(); } } private class Updater implements Runnable { Updater() {} public void run() { updateContents(); } } }