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-2007 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: package com.sun.rave.web.ui.component;
042:
043: import com.sun.rave.web.ui.theme.ThemeImages;
044: import com.sun.rave.web.ui.component.util.event.CommandEvent;
045:
046: import java.util.ArrayList;
047: import java.util.Iterator;
048: import java.util.List;
049: import java.util.Map;
050: import java.util.HashMap;
051: import java.util.Stack;
052:
053: import javax.faces.component.UIComponent;
054: import javax.faces.context.FacesContext;
055: import javax.faces.event.AbortProcessingException;
056: import javax.faces.event.ActionEvent;
057: import javax.faces.event.ActionListener;
058: import javax.faces.el.MethodBinding;
059:
060: /**
061: * <p> This class defines a <code>TreeNode UIComponent</code>. This component
062: * is expected to exist as a child of a {@link Tree} or another
063: * <code>TreeNode</code>.</p>
064: *
065: * @author Ken Paulsen
066: */
067: public class TreeNode extends TreeNodeBase {
068:
069: /**
070: * Constructor.
071: */
072: public TreeNode() {
073: super ();
074: setLayoutDefinitionKey(LAYOUT_KEY);
075: }
076:
077: /**
078: * <p> This method determines the theme images that should be drawn from
079: * left to right (0 to x) when rendering the lines before the text
080: * for this node.</p>
081: *
082: * @return A <code>List</code> of Strings that represent theme keys for
083: * the images to be drawn. The first list element is the first
084: * image to display when rendering left to right.
085: */
086: public List getImageKeys() {
087: // Walk backward up the tree, calculate the theme image
088: Stack stack = new Stack();
089: Object value = null;
090: Map attributes = null;
091: boolean last = false;
092: boolean first = true;
093: boolean bottomNode = false;
094: //value = attributes.get("firstChild");
095: //boolean topNode = (value == null) ? true : value.toString().equals("true");
096: for (TreeNode node = getParentTreeNode(this ); node != null; node = getParentTreeNode(node)) {
097: attributes = node.getAttributes();
098: //topNode = (""+attributes.get("firstChild")).equals("true");
099: bottomNode = ("" + attributes.get("lastChild"))
100: .equals("true");
101: if (first) {
102: // Direct parent is special
103: first = false;
104: if (getChildren().size() > 0) {
105: // For this property, we use 'this' for attributes
106: if (((Boolean) getAttributes().get("expanded"))
107: .booleanValue()) {
108: // if (topNode) {
109: // stack.push(ThemeImages.TREE_HANDLE_DOWN_TOP);
110: // } else
111: if (bottomNode) {
112: stack
113: .push(ThemeImages.TREE_HANDLE_DOWN_LAST);
114: } else {
115: stack
116: .push(ThemeImages.TREE_HANDLE_DOWN_MIDDLE);
117: }
118: } else {
119: // if (topNode) {
120: // stack.push(ThemeImages.TREE_HANDLE_RIGHT_TOP);
121: // } else
122: if (bottomNode) {
123: stack
124: .push(ThemeImages.TREE_HANDLE_RIGHT_LAST);
125: } else {
126: stack
127: .push(ThemeImages.TREE_HANDLE_RIGHT_MIDDLE);
128: }
129: }
130: //ThemeImages.TREE_HANDLE_DOWN_TOP_NOSIBLING
131: //ThemeImages.TREE_HANDLE_RIGHT_TOP_NOSIBLING
132: } else {
133: // if (topNode) {
134: // stack.push(ThemeImages.TREE_LINE_FIRST_NODE);
135: // } else
136: if (bottomNode) {
137: stack.push(ThemeImages.TREE_LINE_LAST_NODE);
138: } else {
139: stack.push(ThemeImages.TREE_LINE_MIDDLE_NODE);
140: }
141: }
142: } else {
143: // We get the attributes this way because we really want to parent's values
144: // to see if we have a peer
145: value = node.getAttributes().get("lastChild");
146: last = (value == null) ? true : value.toString()
147: .equals("true");
148: if (last || (node.getChildren().size() == 0)) {
149: stack.push(ThemeImages.TREE_BLANK);
150: } else {
151: stack.push(ThemeImages.TREE_LINE_VERTICAL);
152: }
153: }
154: }
155:
156: // Handle special case where this.getParent() is the root node...
157: // don't draw a line up to it unless the root node has an icon.
158: TreeNode parent = getParentTreeNode(this );
159: if (parent instanceof Tree) {
160: // Ok, so this is a direct child of the root... but is it the first?
161: Iterator children = parent.getChildren().iterator();
162: Object child = null;
163: while (children.hasNext()) {
164: child = children.next();
165: if (child instanceof TreeNode) {
166: // Check to see if the child is 'this'
167: if (child == this ) {
168: // Ok, so this is the child that is effected... make
169: // sure the root node doesn't have an icon
170: String imgURL = parent.getImageURL();
171: if (((imgURL == null) || imgURL.equals(""))
172: && (parent.getFacet(IMAGE_FACET_KEY) == null)) {
173: // This is the special case
174: // Get the top image and change it
175: stack.push(topLineImageMapping.get(stack
176: .pop()));
177: }
178: }
179: // break b/c we only want to check the first TreeNode
180: break;
181: }
182: }
183: }
184:
185: // Reverse the order
186: List list = new ArrayList();
187: while (!stack.empty()) {
188: list.add(stack.pop());
189: }
190:
191: // Return the list
192: return list;
193: }
194:
195: /**
196: * <p> This method returns the closest parent that is a TreeNode, or null
197: * if not found.</p>
198: *
199: * @param node The starting <code>TreeNode</code>.
200: *
201: * @return The clost parent <code>TreeNode</code>
202: */
203: public static TreeNode getParentTreeNode(UIComponent node) {
204: node = node.getParent();
205: while ((node != null) && !(node instanceof TreeNode)) {
206: node = node.getParent();
207: }
208: return (TreeNode) node;
209: }
210:
211: /**
212: * <p> This <code>ActionListener</code> is invoked when the
213: * <code>TreeNode</code> expand/collapse icon is clicked. It will
214: * first attempt to invoke user-defined
215: * {@link com.sun.rave.web.ui.component.until.event.Handler}s, then will
216: * perform the default operation of expanding or collapsing this
217: * <code>TreeNode</code>. If a user-defined
218: * {@link com.sun.rave.web.ui.component.until.event.Handler} returns
219: * "false", the default functionality will not be performed.</p>
220: *
221: * @param event The <code>ActionEvent</code>.
222: */
223: public void toggleNode(ActionEvent event)
224: throws AbortProcessingException {
225: // Dispatch any user-defined Events
226: FacesContext context = FacesContext.getCurrentInstance();
227: Object val = getLayoutDefinition(context).dispatchHandlers(
228: context, "toggle", new CommandEvent(this , event));
229:
230: // broadcast the turner click event
231: ActionEvent newEvent = new ActionEvent(this );
232: fireTurnerEvent(newEvent);
233:
234: // If user-defined handler return value is "false", skip default action
235: boolean cont = true;
236: if (val != null) {
237: cont = !val.toString().trim().equalsIgnoreCase("false"); // NOI18N
238: }
239:
240: if (cont) {
241: // Next Toggle the Icon
242: setExpanded(!isExpanded());
243: Object src = event.getSource();
244: if (src instanceof UIComponent) {
245: Map attributes = ((UIComponent) src).getAttributes();
246: attributes.put("icon",
247: getHandleIcon((String) attributes.get("icon")));
248: }
249: }
250: }
251:
252: /**
253: * <p>Add an action listener instance for the IconHyperlink representing
254: * this node's turner.</p>
255: *
256: * @param listener The ActionListener instance to register for turner
257: * IconHyperlink clicks.
258: */
259: public void addActionListener(ActionListener listener) {
260: addFacesListener(listener);
261: }
262:
263: /**
264: * <p>Get all ActionListener instances for this node's turner IconHyperlink
265: * click.</p>
266: *
267: * @return ActionListener[] The list of listeners for this node's turner
268: * IconHyperlink click.
269: */
270: public ActionListener[] getActionListeners() {
271: ActionListener al[] = (ActionListener[]) getFacesListeners(ActionListener.class);
272: return (al);
273: }
274:
275: /**
276: * <p>Remove an action listener instance from the list for this node's
277: * turner IconHyperlink.</p>
278: *
279: * @param listener The ActionListener instance to remove.
280: */
281: public void removeActionListener(ActionListener listener) {
282: removeFacesListener(listener);
283: }
284:
285: public void fireTurnerEvent(ActionEvent event)
286: throws AbortProcessingException {
287: // fire the turner event to all registered action listener instances
288: super .broadcast(event);
289:
290: FacesContext context = getFacesContext();
291:
292: // Notify the specified action listener method (if any)
293: MethodBinding mb = getActionListener();
294: if (mb != null) {
295: mb.invoke(context, new Object[] { event });
296: }
297: }
298:
299: /**
300: * <p> This method enables the icon to switch from expanded to collapsed,
301: * or from collapsed to expanded depending on the current state of
302: * this component.</p>
303: *
304: * @param value The current value of the Icon. It will use the current
305: * value to re-use first/last information from the old key.
306: *
307: * @return The new (or same if the state hasn't changed) icon state
308: */
309: protected String getHandleIcon(String value) {
310: // Make sure we have a value
311: if ((value == null) || value.trim().equals("")) {
312: value = ThemeImages.TREE_HANDLE_RIGHT_TOP_NOSIBLING;
313: }
314:
315: // Convert it to the current state
316: if (isExpanded()) {
317: // RIGHT to DOWN
318: value = value.replaceFirst("RIGHT", "DOWN");
319: } else {
320: // DOWN to RIGHT
321: value = value.replaceFirst("DOWN", "RIGHT");
322: }
323: return value;
324: }
325:
326: /**
327: * <p> This Map maps the standard first line image icon to the "special
328: * case" one. The special case is when the root is not visible, the
329: * icon directly below where the root should be looks different than
330: * all others.</p>
331: */
332: private static Map topLineImageMapping = new HashMap(6);
333: static {
334: topLineImageMapping.put(ThemeImages.TREE_HANDLE_DOWN_MIDDLE,
335: ThemeImages.TREE_HANDLE_DOWN_TOP);
336: topLineImageMapping.put(ThemeImages.TREE_HANDLE_DOWN_LAST,
337: ThemeImages.TREE_HANDLE_DOWN_TOP_NOSIBLING);
338: topLineImageMapping.put(ThemeImages.TREE_HANDLE_RIGHT_MIDDLE,
339: ThemeImages.TREE_HANDLE_RIGHT_TOP);
340: topLineImageMapping.put(ThemeImages.TREE_HANDLE_RIGHT_LAST,
341: ThemeImages.TREE_HANDLE_RIGHT_TOP_NOSIBLING);
342: topLineImageMapping.put(ThemeImages.TREE_LINE_MIDDLE_NODE,
343: ThemeImages.TREE_LINE_FIRST_NODE);
344: topLineImageMapping.put(ThemeImages.TREE_LINE_LAST_NODE,
345: ThemeImages.TREE_BLANK);
346: }
347:
348: /**
349: * <p> This is the facet key used to set a custom image for this
350: * <code>TreeNode</code>. (image)</p>
351: */
352: public static final String IMAGE_FACET_KEY = "image";
353:
354: /**
355: * <p> This is the facet key used to define the content for the
356: * </code>TreeNode</code>. (content)</p>
357: */
358: public static final String CONTENT_FACET_KEY = "content";
359:
360: /**
361: * <p> This is the location of the XML file that declares the layout for
362: * the PanelGroup. (layout/tree.xml)</p>
363: */
364: public static final String LAYOUT_KEY = "layout/treeNode.xml";
365: }
|