001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.modules.form.palette;
043:
044: import java.awt.*;
045: import javax.swing.*;
046: import javax.swing.event.*;
047: import java.lang.reflect.Field;
048:
049: import org.openide.nodes.*;
050: import org.openide.util.*;
051: import org.openide.explorer.view.MenuView;
052:
053: /**
054: * Hacked MenuView.Menu to use ScrollablePopupMenu instead of JPopupMenu
055: * as its popup, and to filter invalid palette item nodes.
056: */
057:
058: public class PaletteMenuView extends org.openide.awt.JMenuPlus {
059:
060: private Node menuNode;
061: private NodeAcceptor menuAction;
062:
063: private boolean hacked = false;
064: private boolean filled = false;
065: private int level;
066:
067: private static int maxHeight = Utilities.getUsableScreenBounds().height - 25;
068:
069: public PaletteMenuView(NodeAcceptor acceptor) {
070: this (PaletteUtils.getPaletteNode(), acceptor);
071: }
072:
073: public PaletteMenuView(Node node, NodeAcceptor acceptor) {
074: this (node, acceptor, 0);
075: }
076:
077: private PaletteMenuView(Node node, NodeAcceptor acceptor, int level) {
078: menuNode = node;
079: menuAction = acceptor;
080: this .level = level;
081: setText(node.getDisplayName());
082: getSubNodes(); // force subnodes creation
083: }
084:
085: /** popupMenu field should be set here because getPopupMenu() is called from
086: * superclass constructor.
087: */
088: @Override
089: public JPopupMenu getPopupMenu() {
090: if (!hacked) {
091: try {
092: Field f = JMenu.class.getDeclaredField("popupMenu"); // NOI18N
093: f.setAccessible(true);
094: if (f.get(this ) == null) {
095: ScrollPopupMenu popup = new ScrollPopupMenu(
096: maxHeight);
097: popup.setInvoker(this );
098: f.set(this , popup);
099: }
100: hacked = true;
101: } catch (Exception ex) {
102: System.out
103: .println("[WARNING] Cannot create scrollable popup menu."); // NOI18N
104: }
105: }
106:
107: JPopupMenu popup = super .getPopupMenu();
108: fillSubMenu(popup);
109: return popup;
110: }
111:
112: private void fillSubMenu(JPopupMenu popup) {
113: if (!filled) {
114: filled = true;
115: popup.addPopupMenuListener(new PopupListener(popup));
116: removeAll();
117:
118: Node[] nodes = getSubNodes();
119: if (nodes.length > 0) {
120: for (int i = 0; i < nodes.length; i++)
121: add(nodes[i].isLeaf() ? (JMenuItem) new MenuView.MenuItem(
122: nodes[i], menuAction)
123: : (JMenuItem) new PaletteMenuView(nodes[i],
124: menuAction, level + 1));
125: } else {
126: JMenuItem empty = new JMenuItem(PaletteUtils
127: .getBundleString("CTL_EmptyPaletteMenu")); // NOI18N
128: empty.setEnabled(false);
129: add(empty);
130: }
131: }
132: }
133:
134: private Node[] getSubNodes() {
135: return level == 0 ? PaletteUtils.getCategoryNodes(menuNode,
136: true) : PaletteUtils.getItemNodes(menuNode, true);
137: }
138:
139: private class PopupListener implements PopupMenuListener {
140: private JPopupMenu popup;
141:
142: PopupListener(JPopupMenu popup) {
143: this .popup = popup;
144: }
145:
146: public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
147: filled = false; // clear the status and stop listening
148: popup.removePopupMenuListener(this );
149: }
150:
151: public void popupMenuCanceled(PopupMenuEvent e) {
152: }
153:
154: public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
155: }
156: }
157:
158: protected Point getPopupMenuOrigin() {
159: int x = 0;
160: int y = 0;
161: JPopupMenu pm = getPopupMenu();
162: // Figure out the sizes needed to caclulate the menu position
163: Dimension screenSize = Toolkit.getDefaultToolkit()
164: .getScreenSize();
165: Dimension s = getSize();
166: Dimension pmSize = pm.getSize();
167: // For the first time the menu is popped up,
168: // the size has not yet been initiated
169: if (pmSize.width == 0) {
170: pmSize = pm.getPreferredSize();
171: }
172: if (pmSize.height > maxHeight) {
173: pmSize.height = maxHeight + 2;
174: pmSize.width += 14;
175: }
176:
177: Point position = getLocationOnScreen();
178:
179: Container parent = getParent();
180: if (parent instanceof JPopupMenu) {
181: // We are a submenu (pull-right)
182:
183: if (getComponentOrientation().isLeftToRight()) {
184: // First determine x:
185: if (position.x + s.width + pmSize.width < screenSize.width) {
186: x = s.width; // Prefer placement to the right
187: } else {
188: x = 0 - pmSize.width; // Otherwise place to the left
189: }
190: } else {
191: // First determine x:
192: if (position.x < pmSize.width) {
193: x = s.width; // Prefer placement to the right
194: } else {
195: x = 0 - pmSize.width; // Otherwise place to the left
196: }
197: }
198: // Then the y:
199: if (position.y + pmSize.height < screenSize.height) {
200: y = 0; // Prefer dropping down
201: } else {
202: y = s.height - pmSize.height; // Otherwise drop 'up'
203: if (y < -position.y)
204: y = -position.y + 6;
205: }
206: } else {
207: // We are a toplevel menu (pull-down)
208:
209: if (getComponentOrientation().isLeftToRight()) {
210: // First determine the x:
211: if (position.x + pmSize.width < screenSize.width) {
212: x = 0; // Prefer extending to right
213: } else {
214: x = s.width - pmSize.width; // Otherwise extend to left
215: }
216: } else {
217: // First determine the x:
218: if (position.x + s.width < pmSize.width) {
219: x = 0; // Prefer extending to right
220: } else {
221: x = s.width - pmSize.width; // Otherwise extend to left
222: }
223: }
224: // Then the y:
225: if (position.y + s.height + pmSize.height < screenSize.height) {
226: y = s.height; // Prefer dropping down
227: } else {
228: y = -pmSize.height; // Otherwise drop 'up'
229: }
230: }
231: return new Point(x, y);
232: }
233: }
|