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:
042: package org.netbeans.modules.xml.xam.ui.column;
043:
044: import java.awt.BorderLayout;
045: import java.awt.Color;
046: import java.awt.Component;
047: import java.awt.Dimension;
048: import java.awt.Graphics;
049: import java.awt.GridBagConstraints;
050: import java.awt.GridBagLayout;
051: import java.awt.GridLayout;
052: import java.awt.Insets;
053: import java.awt.Point;
054: import java.awt.Rectangle;
055: import java.awt.event.ActionEvent;
056: import java.awt.event.ActionListener;
057: import java.util.HashMap;
058: import java.util.Iterator;
059: import java.util.Map;
060: import java.util.Map.Entry;
061: import java.util.Set;
062: import javax.accessibility.AccessibleContext;
063: import javax.swing.AbstractAction;
064: import javax.swing.BorderFactory;
065: import javax.swing.Box;
066: import javax.swing.Icon;
067: import javax.swing.JButton;
068: import javax.swing.JLabel;
069: import javax.swing.JPanel;
070: import javax.swing.JScrollPane;
071: import javax.swing.JViewport;
072: import javax.swing.UIManager;
073: import javax.swing.border.Border;
074: import javax.swing.border.EmptyBorder;
075: import javax.swing.event.ChangeEvent;
076: import javax.swing.event.ChangeListener;
077:
078: /**
079: * Contains and manages the link buttons that act as bread crumbs
080: * for quickly browsing an instance of ColumnView.
081: *
082: * @author Nathan Fiedler
083: */
084: public class LinkPanel extends JPanel implements ActionListener {
085: private static final long serialVersionUID = 1L;
086: /** Occupies space to the right of the buttons. */
087: private Component layoutFiller;
088: /** Mapping of buttons to columns. */
089: private Map<LinkButton, Column> buttonColumnMap;
090: /** Scroll pane to manage, if non-null. */
091: private JScrollPane scrollPane;
092: /** The column view to scroll. */
093: private ColumnView columnView;
094: /** The container for the links. */
095: private JPanel linkPanel;
096: private static final int ICON_WIDTH = 11;
097: private static final int ICON_HEIGHT = 11;
098: private static final int[] xpoints = new int[20];
099: private static final int[] ypoints = new int[20];
100:
101: /**
102: * Creates a new instance of LinkPanel.
103: *
104: * @param view the column view to scroll.
105: */
106: public LinkPanel(ColumnView view) {
107: super (new BorderLayout());
108: linkPanel = new JPanel(new GridBagLayout());
109: scrollPane = new JScrollPane(linkPanel,
110: JScrollPane.VERTICAL_SCROLLBAR_NEVER,
111: JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
112: scrollPane.setBorder(null);
113: add(scrollPane, BorderLayout.CENTER);
114: columnView = view;
115: // Try to use the toolbar border defined in NetBeans core.
116: Border b = (Border) UIManager.get("Nb.Editor.Toolbar.border"); //NOI18N
117: if (b == null) {
118: // But, fall back on having something rather than nothing.
119: b = BorderFactory
120: .createMatteBorder(0, 0, 1, 0, Color.BLACK);
121: }
122: setBorder(b);
123: buttonColumnMap = new HashMap<LinkButton, Column>();
124:
125: // Configure the scrolling buttons.
126: JButton left = new TimerButton(new ScrollLeftAction(scrollPane));
127: JButton right = new TimerButton(new ScrollRightAction(
128: scrollPane));
129: configureButton(left, new LeftIcon());
130: configureButton(right, new RightIcon());
131: left.setPreferredSize(new Dimension(17, 17));
132: right.setPreferredSize(new Dimension(17, 17));
133: JPanel buttonPanel = new JPanel(new GridLayout(1, 0));
134: buttonPanel.setBorder(new EmptyBorder(0, 3, 1, 2));
135: buttonPanel.add(left);
136: buttonPanel.add(right);
137: add(buttonPanel, BorderLayout.EAST);
138: }
139:
140: public void actionPerformed(ActionEvent e) {
141: Object src = e.getSource();
142: if (src instanceof LinkButton) {
143: LinkButton button = (LinkButton) src;
144: // Scroll the link button to the center, which is in
145: // addition to the default scroll pane behavior of making
146: // the focused component visible.
147: if (scrollPane != null) {
148: JViewport vp = scrollPane.getViewport();
149: Rectangle visRect = vp.getViewRect();
150: Rectangle compRect = button.getBounds();
151: Component view = vp.getView();
152: visRect.x = Math.max(0, Math.min(compRect.x
153: - (visRect.width - compRect.width) / 2, view
154: .getWidth()
155: - visRect.width));
156: vp.scrollRectToVisible(visRect);
157: }
158: // Scroll to the corresponding column.
159: Column column = buttonColumnMap.get(button);
160: columnView.scrollToColumn(column, true);
161: }
162: }
163:
164: /**
165: * Adds a link button to the panel for the given column.
166: *
167: * @param column Column for which to add button.
168: */
169: public void appendLink(Column column) {
170: if (linkPanel.getComponentCount() > 0) {
171: // There are other links, we need to add '>' now.
172: GridBagConstraints gbc = new GridBagConstraints();
173: // Pad for five pixels on either side, taking account
174: // of the 3 pixel inset of the button (below). Note that
175: // > character has two blank pixels on the left.
176: gbc.insets = new Insets(0, 3, 2, 0);
177: linkPanel.add(new JLabel(">"), gbc); // NOI18N
178: }
179: LinkButton button = new LinkButton(column.getTitle());
180: AccessibleContext ac = button.getAccessibleContext();
181: ac.setAccessibleName(column.getTitle());
182: ac.setAccessibleDescription(column.getDescription());
183: button.addActionListener(this );
184: buttonColumnMap.put(button, column);
185: GridBagConstraints gbc = new GridBagConstraints();
186: gbc.insets = new Insets(0, 3, 0, 0);
187: linkPanel.add(button, gbc);
188: // Fill the space to the right so the links will be left-aligned.
189: if (layoutFiller != null) {
190: linkPanel.remove(layoutFiller);
191: } else {
192: layoutFiller = Box.createHorizontalGlue();
193: }
194: gbc.fill = GridBagConstraints.HORIZONTAL;
195: gbc.weightx = 1.0d;
196: linkPanel.add(layoutFiller, gbc);
197: linkPanel.revalidate();
198: linkPanel.repaint();
199: }
200:
201: /**
202: * Removes all of the links from the panel.
203: */
204: public void clearLinks() {
205: linkPanel.removeAll();
206: buttonColumnMap.clear();
207: linkPanel.revalidate();
208: linkPanel.repaint();
209: }
210:
211: /**
212: * Configure a button for the link panel.
213: *
214: * @param button the button.
215: * @param icon icon for the button.
216: */
217: private static void configureButton(JButton button, Icon icon) {
218: button.setIcon(icon);
219: button.setMargin(null);
220: button.setText(null);
221: button.setFocusable(false);
222: }
223:
224: /**
225: * Remove the links from the panel, starting at the given offset.
226: *
227: * @param index link index from which to start removing.
228: */
229: public void truncateLinks(int index) {
230: // Account for the layout filler, which we want to preserve.
231: int count = linkPanel.getComponentCount() - 1;
232: // Account for the separators between the buttons.
233: index = index * 2 - 1;
234: while (count > index) {
235: Component child = linkPanel.getComponent(index);
236: if (child instanceof LinkButton) {
237: buttonColumnMap.remove((LinkButton) child);
238: }
239: linkPanel.remove(index);
240: count--;
241: }
242: linkPanel.revalidate();
243: linkPanel.repaint();
244: }
245:
246: /**
247: * Update the link title to reflect a change in the column.
248: *
249: * @param column Column for which to update link text.
250: */
251: public void updateLink(Column column) {
252: Set<Entry<LinkButton, Column>> entries = buttonColumnMap
253: .entrySet();
254: Iterator<Entry<LinkButton, Column>> iter = entries.iterator();
255: while (iter.hasNext()) {
256: Entry<LinkButton, Column> entry = iter.next();
257: if (entry.getValue().equals(column)) {
258: LinkButton button = entry.getKey();
259: button.setText(column.getTitle());
260: }
261: }
262: }
263:
264: /**
265: * Scrolls the link panel to the left.
266: */
267: private static class ScrollLeftAction extends AbstractAction
268: implements ChangeListener {
269: /** silence compiler warnings */
270: private static final long serialVersionUID = 1L;
271: /** The pane to be scrolled. */
272: private JScrollPane pane;
273:
274: /**
275: * Creates a new instance of ScrollLeftAction.
276: *
277: * @param pane the scroll pane to manage.
278: */
279: public ScrollLeftAction(JScrollPane pane) {
280: super ();
281: this .pane = pane;
282: pane.getViewport().addChangeListener(this );
283: }
284:
285: public void actionPerformed(ActionEvent e) {
286: JViewport vp = pane.getViewport();
287: Dimension size = vp.getExtentSize();
288: Point p = vp.getViewPosition();
289: p.x -= (size.width / 10);
290: if (p.x < 0) {
291: p.x = 0;
292: }
293: vp.setViewPosition(p);
294: }
295:
296: public void stateChanged(ChangeEvent e) {
297: JViewport vp = pane.getViewport();
298: Point p = vp.getViewPosition();
299: setEnabled(p.x > 0);
300: }
301: }
302:
303: /**
304: * Scrolls the link panel to the right.
305: */
306: private static class ScrollRightAction extends AbstractAction
307: implements ChangeListener {
308: /** silence compiler warnings */
309: private static final long serialVersionUID = 1L;
310: /** The pane to be scrolled. */
311: private JScrollPane pane;
312:
313: /**
314: * Creates a new instance of ScrollRightAction.
315: *
316: * @param pane the scroll pane to manage.
317: */
318: public ScrollRightAction(JScrollPane pane) {
319: super ();
320: this .pane = pane;
321: pane.getViewport().addChangeListener(this );
322: }
323:
324: public void actionPerformed(ActionEvent e) {
325: JViewport vp = pane.getViewport();
326: Dimension size = vp.getExtentSize();
327: Point p = vp.getViewPosition();
328: p.x += (size.width / 10);
329: int max = vp.getViewSize().width - size.width;
330: if (p.x > max) {
331: p.x = max;
332: }
333: vp.setViewPosition(p);
334: }
335:
336: public void stateChanged(ChangeEvent e) {
337: JViewport vp = pane.getViewport();
338: Dimension size = vp.getExtentSize();
339: Point p = vp.getViewPosition();
340: int max = vp.getViewSize().width - size.width;
341: setEnabled(p.x < max);
342: }
343: }
344:
345: /**
346: * Copied from core/swing/tabcontrol; paints a left arrow.
347: */
348: private static class LeftIcon implements Icon {
349:
350: public int getIconHeight() {
351: return ICON_HEIGHT;
352: }
353:
354: public int getIconWidth() {
355: return ICON_WIDTH;
356: }
357:
358: public void paintIcon(Component c, Graphics g, int x, int y) {
359: y -= 2;
360: g.setColor(c.isEnabled() ? c.getForeground() : UIManager
361: .getColor("controlShadow")); //NOI18N
362: int wid = getIconWidth();
363: int hi = getIconHeight() + 1;
364: xpoints[0] = x + (wid - 4);
365: ypoints[0] = y + 2;
366: xpoints[1] = xpoints[0];
367: ypoints[1] = y + hi + 1;
368: xpoints[2] = x + 2;
369: ypoints[2] = y + (hi / 2) + 1;
370: g.fillPolygon(xpoints, ypoints, 3);
371: }
372: }
373:
374: /**
375: * Copied from core/swing/tabcontrol; paints a right arrow.
376: */
377: private static class RightIcon implements Icon {
378:
379: public int getIconWidth() {
380: return ICON_WIDTH;
381: }
382:
383: public int getIconHeight() {
384: return ICON_HEIGHT - 2;
385: }
386:
387: public void paintIcon(Component c, Graphics g, int x, int y) {
388: y -= 2;
389: g.setColor(c.isEnabled() ? c.getForeground() : UIManager
390: .getColor("controlShadow")); //NOI18N
391: int wid = getIconWidth();
392: int hi = getIconHeight() + 1;
393: xpoints[0] = x + 3;
394: ypoints[0] = y + 1;
395: xpoints[1] = x + 3;
396: ypoints[1] = y + hi + 1;
397: xpoints[2] = x + (wid - 4) + 1;
398: ypoints[2] = y + (hi / 2) + 1;
399: g.fillPolygon(xpoints, ypoints, 3);
400: }
401: }
402: }
|