001: package com.xoetrope.swing;
002:
003: import java.awt.BasicStroke;
004: import java.awt.BorderLayout;
005: import java.awt.Color;
006: import java.awt.Component;
007: import java.awt.FlowLayout;
008: import java.awt.Graphics2D;
009: import java.awt.Rectangle;
010: import java.awt.RenderingHints;
011: import java.awt.event.ActionEvent;
012: import java.awt.event.ActionListener;
013: import java.awt.image.BufferedImage;
014: import java.util.ArrayList;
015: import javax.swing.Icon;
016: import javax.swing.ImageIcon;
017: import javax.swing.JPanel;
018: import net.xoetrope.swing.XPanel;
019: import net.xoetrope.xui.XPageManager;
020: import net.xoetrope.xui.XProject;
021: import net.xoetrope.xui.XProjectManager;
022: import net.xoetrope.xui.style.XStyle;
023: import net.xoetrope.xui.style.XStyleComponent;
024:
025: /**
026: * <p>
027: * The Breadcrumb bar can be used to present a navigation history by listing
028: * links to the pages used to navigate to the current page.
029: * </p>
030: * <p>If the visible navigation history exceeds the size of the component the
031: * component will scroll the navigation either left or right via arrow keys
032: * automatically prefixed and appended to the component.</p>
033: * <p>When a link is selected an ActionEvent is fired and the listener can
034: * respond to the event. The page name associated with the link is passed with
035: * the event as the action command. Once the link has been clicked the links to
036: * the right of the select link are removed and the bar is refreshed.
037: * </p>
038: * <p><code><pre>
039: * @Find
040: * private XBreadcrumbBar crumbs;
041: *
042: * ...
043: * crumbs.addLink( "Home", "Home", "Go to the home page", "home.png" );
044: * crumbs.updateNavButtons();
045: * </pre></code></p>
046: *
047: *
048: * <p> Copyright (c) Xoetrope Ltd., 2001-2006, This software is licensed under
049: * the GNU Public License (GPL), please see license.txt for more details. If
050: * you make commercial use of this software you must purchase a commercial
051: * license from Xoetrope.</p>
052: * <p> $Revision: 1.5 $</p>
053: */
054: public class XBreadcrumbBar extends XPanel implements ActionListener,
055: XStyleComponent {
056: protected ArrayList links;
057: protected XHyperLabel prevButton;
058: protected XHyperLabel nextButton;
059: protected XPageManager pageMgr;
060: protected ActionListener actionListener;
061:
062: private String selectedLink;
063: private JPanel leftPanel;
064: private String styleName;
065: private XProject currentProject;
066: private ImageIcon separatorImage;
067:
068: /**
069: * Creates a new instance of XBreadcrumbBar
070: */
071: public XBreadcrumbBar() {
072: currentProject = XProjectManager.getCurrentProject();
073: setLayout(new BorderLayout(0, 0));
074: leftPanel = new JPanel();
075: leftPanel.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0));
076: links = new ArrayList();
077: pageMgr = currentProject.getPageManager();
078: styleName = "";
079:
080: prevButton = new XHyperLabel();
081: nextButton = new XHyperLabel();
082: prevButton.setStyle(styleName);
083: nextButton.setStyle(styleName);
084: prevButton.setText("<");
085: nextButton.setText(">");
086: prevButton.setVisible(false);
087: nextButton.setVisible(false);
088: prevButton.addActionListener(this );
089: nextButton.addActionListener(this );
090: leftPanel.add(prevButton);
091: add(nextButton, BorderLayout.EAST);
092: add(leftPanel, BorderLayout.CENTER);
093: }
094:
095: /**
096: * Set the component style
097: * @param name the new style name
098: */
099: public void setStyle(String name) {
100: styleName = name;
101: Component[] children = leftPanel.getComponents();
102: int numChildren = children.length;
103: for (int i = 1; i < numChildren; i++)
104: ((XHyperLabel) children[i]).setStyle(name);
105:
106: XStyle xstyle = currentProject.getStyleManager().getStyle(
107: styleName);
108: Color bkColor = xstyle.getStyleAsColor(XStyle.COLOR_BACK);
109: setBackground(bkColor);
110: leftPanel.setBackground(bkColor);
111: }
112:
113: /**
114: * Respond to the button click events
115: */
116: public void actionPerformed(ActionEvent ae) {
117: Object src = ae.getSource();
118: if (src == prevButton)
119: showEarlierLinks();
120: else if (src == nextButton)
121: showLaterLinks();
122: else {
123: selectedLink = null;
124: Component[] children = leftPanel.getComponents();
125: int numChildren = children.length;
126: for (int i = 1; i < numChildren; i++) {
127: if (src == children[i]) {
128: selectedLink = (String) links.get(i - 1);
129: continue;
130: }
131:
132: // If the page has been found, remove the following links. The
133: // links are not removed in order.
134: if (selectedLink != null) {
135: links.remove(links.size() - 1);
136: leftPanel.remove(children[i]);
137: }
138: }
139: doLayout();
140: repaint();
141: if (selectedLink != null)
142: fireActionPerformed();
143: }
144: }
145:
146: /**
147: * Display the earlier (hidden) links
148: */
149: public void showEarlierLinks() {
150: Component[] children = leftPanel.getComponents();
151: int numChildren = children.length;
152: for (int i = 1; i < numChildren; i++) {
153: // Show the last of the leading hidden components
154: if (children[i].isVisible()) {
155: if (i > 1)
156: children[i - 1].setVisible(true);
157: prevButton.setVisible(i != 2);
158: updateNavButtons();
159: doLayout();
160: repaint();
161: break;
162: }
163: }
164: }
165:
166: /**
167: * Display the later links by hiding earlier links
168: */
169: public void showLaterLinks() {
170: Component[] children = leftPanel.getComponents();
171: int numChildren = children.length;
172: for (int i = 1; i < numChildren; i++) {
173: // Hide the last of the leading visible components
174: if (children[i].isVisible()) {
175: children[i].setVisible(false);
176: prevButton.setVisible(true);
177: updateNavButtons();
178: doLayout();
179: repaint();
180: break;
181: }
182: }
183: }
184:
185: public void setLinks(String links) {
186: String[] items = links.split(",");
187: for (int i = 0; i < items.length; i++) {
188: String[] values = items[i].split("/");
189: addLink(values[0], values.length > 1 ? values[1] : null,
190: values.length > 2 ? values[2] : null,
191: values.length > 3 ? values[3] : null);
192: }
193: }
194:
195: /**
196: * Add a new link. The new link will be appended to the breadcrumb trail.
197: * @param caption the component caption (or localization key) as displayed to the end user
198: * @param pageName the page to which the application will move to if the new link is selected
199: */
200: public void addLink(String caption, String pageName) {
201: addLink(caption, pageName, null, null);
202: }
203:
204: /**
205: * Add a new link. The new link will be appended to the breadcrumb trail.
206: * @param caption the component caption (or localization key) as displayed to the end user
207: * @param pageName the page to which the application will move to if the new link is selected
208: * @param tooltip the tooltip text or null if none is specified
209: * @param iconName the link icon or null if none is specified
210: */
211: public void addLink(String caption, String pageName,
212: String tooltip, String iconName) {
213: XHyperLabel link = new XHyperLabel();
214:
215: link.setStyle(styleName);
216: link.setText(caption);
217: link.addActionListener(this );
218:
219: if (tooltip != null)
220: link.setToolTipText(tooltip);
221:
222: if (iconName != null)
223: link.setIcon((Icon) currentProject.getIcon(iconName));
224: else if (links.size() > 0)
225: link.setIcon(getSeparatorImage());
226:
227: link.setIconTextGap(2);
228:
229: leftPanel.add(link);
230: links.add(pageName);
231:
232: // Don't try this if the component hasn't been initialized
233: if (isVisible() && isValid())
234: updateNavButtons();
235: }
236:
237: /**
238: * Repaint the component once it has been created
239: */
240: public void addNotify() {
241: super .addNotify();
242: updateNavButtons();
243: }
244:
245: /**
246: * Update the navigation buttons accoriding to the available width;
247: */
248: public void updateNavButtons() {
249: validate();
250:
251: int length = getPreferredSize().width;
252: int width = getWidth();
253: if (length > width) {
254: if (nextButton.isVisible())
255: return;
256: nextButton.setVisible(true);
257: } else
258: nextButton.setVisible(false);
259: doLayout();
260: repaint();
261: }
262:
263: public void showAll() {
264: Component[] children = leftPanel.getComponents();
265: int numChildren = children.length;
266: for (int i = 1; i < numChildren; i++)
267: children[i].setVisible(true);
268:
269: prevButton.setVisible(false);
270: updateNavButtons();
271: }
272:
273: /**
274: * Intercept the size change to reset the navigation buttons
275: */
276: public void setBounds(Rectangle r) {
277: super .setBounds(r);
278: showAll();
279: }
280:
281: /**
282: * Intercept the size change to reset the navigation buttons
283: */
284: public void setBounds(int x, int y, int w, int h) {
285: super .setBounds(x, y, w, h);
286: showAll();
287: }
288:
289: /**
290: * Adds an <code>ActionListener</code> to the button.
291: * @param l the <code>ActionListener</code> to be added
292: */
293: public void addActionListener(ActionListener l) {
294: listenerList.add(ActionListener.class, l);
295: }
296:
297: /**
298: * Removes an <code>ActionListener</code> from the button.
299: * If the listener is the currently set <code>Action</code>
300: * for the button, then the <code>Action</code>
301: * is set to <code>null</code>.
302: *
303: * @param l the listener to be removed
304: */
305: public void removeActionListener(ActionListener l) {
306: listenerList.remove(ActionListener.class, l);
307: }
308:
309: /**
310: * Returns an array of all the <code>ActionListener</code>s added
311: * to this AbstractButton with addActionListener().
312: *
313: * @return all of the <code>ActionListener</code>s added or an empty
314: * array if no listeners have been added
315: * @since 1.4
316: */
317: public ActionListener[] getActionListeners() {
318: return (ActionListener[]) (listenerList
319: .getListeners(ActionListener.class));
320: }
321:
322: /**
323: * Notifies all listeners that have registered interest for
324: * notification on this event type. The event instance
325: * is lazily created using the <code>event</code>
326: * parameter.
327: *
328: * @see EventListenerList
329: */
330: protected void fireActionPerformed() {
331: // Guaranteed to return a non-null array
332: Object[] listeners = listenerList.getListenerList();
333: ActionEvent e = null;
334: // Process the listeners last to first, notifying
335: // those that are interested in this event
336: for (int i = listeners.length - 2; i >= 0; i -= 2) {
337: if (listeners[i] instanceof ActionListener) {
338: // Lazily create the event:
339: if (e == null) {
340: e = new ActionEvent(this ,
341: ActionEvent.ACTION_PERFORMED, selectedLink,
342: System.currentTimeMillis(), 0);
343: }
344: ((ActionListener) listeners[i + 1]).actionPerformed(e);
345: }
346: }
347: }
348:
349: /**
350: * Create a separator image
351: * @return the image for the minimize button
352: */
353: private ImageIcon getSeparatorImage() {
354: if (separatorImage == null) {
355: BufferedImage img = new BufferedImage(10, 10,
356: BufferedImage.TYPE_INT_ARGB);
357: Graphics2D g2d = (Graphics2D) img.getGraphics();
358: Object hint = g2d
359: .getRenderingHint(RenderingHints.KEY_RENDERING);
360: g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
361: RenderingHints.VALUE_ANTIALIAS_ON);
362: g2d.setRenderingHint(RenderingHints.KEY_RENDERING,
363: RenderingHints.VALUE_RENDER_QUALITY);
364: g2d.setStroke(new BasicStroke(1.2F));
365: g2d.setColor(getForeground());
366:
367: g2d.fillOval(4, 4, 3, 3);
368: g2d.setRenderingHint(RenderingHints.KEY_RENDERING, hint);
369: g2d.dispose();
370: separatorImage = new ImageIcon(img);
371: }
372: return separatorImage;
373: }
374: }
|