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.Color;
045: import java.awt.Component;
046: import java.awt.Container;
047: import java.awt.Dimension;
048: import java.awt.EventQueue;
049: import java.awt.Point;
050: import java.awt.Rectangle;
051: import java.awt.event.ComponentAdapter;
052: import java.awt.event.ComponentEvent;
053: import java.util.ArrayList;
054: import java.util.List;
055: import java.util.ListIterator;
056: import javax.swing.JComponent;
057: import javax.swing.JPanel;
058: import javax.swing.JViewport;
059: import javax.swing.Scrollable;
060: import org.netbeans.modules.xml.xam.ui.layout.JSplitterBar;
061: import org.netbeans.modules.xml.xam.ui.layout.SplitterLayout;
062:
063: /**
064: * A Swing widget with a horizontal splitter layout. The split bars can
065: * be moved right and left.
066: *
067: * @author Jeri Lockhart
068: * @author Nathan Fiedler
069: */
070: public class BasicColumnView extends JPanel implements ColumnView {
071: private static final String COLUMN_WEIGHT_1 = "1"; // NOI18N
072: private static final int SCROLL_DELAY = 20;
073: static final long serialVersionUID = 1L;
074: /** columns in order from left to right */
075: private List<Column> columnList;
076: /** JSplitterBars in order from left to right */
077: private List<JSplitterBar> splitterList;
078: /** Contains all of the columns and splitters. */
079: private JPanel mainParentPanel;
080:
081: /**
082: * Creates new form BasicColumnView.
083: */
084: public BasicColumnView() {
085: initComponents();
086: columnList = new ArrayList<Column>();
087: splitterList = new ArrayList<JSplitterBar>();
088:
089: mainParentPanel = new MainPanel();
090: mainParentPanel.setBackground(Color.WHITE);
091: mainParentPanel.setLayout(new SplitterLayout(false));
092: scrollPane.setViewportView(mainParentPanel);
093: scrollPane.setViewportBorder(null);
094: scrollPane.getViewport().setBackground(Color.WHITE);
095:
096: addComponentListener(new ComponentAdapter() {
097: public void componentResized(ComponentEvent e) {
098: super .componentResized(e);
099: validate();
100: revalidate();
101: }
102:
103: });
104: }
105:
106: /**
107: * Appends the column to the list, without revalidating or scrolling.
108: *
109: * @param column the Column to add.
110: */
111: protected void appendColumnToList(Column column) {
112: if (column == null) {
113: return;
114: }
115: JComponent comp = column.getComponent();
116: if (comp == null) {
117: return;
118: }
119: columnList.add(column);
120: mainParentPanel.add(COLUMN_WEIGHT_1, comp);
121: JSplitterBar bar = new JSplitterBar();
122: mainParentPanel.add(bar);
123: splitterList.add(bar);
124: }
125:
126: public void appendColumn(Column column) {
127: appendColumnToList(column);
128: // Call validate() which calls SplitterLayout layoutContainer()
129: // layoutContainer() will set a new preferredSize on the
130: // mainParentPanel that will be used by the scroll pane.
131: validate();
132: mainParentPanel.revalidate();
133: scrollToColumn(column, false);
134: }
135:
136: public void appendColumns(Column[] columns) {
137: for (Column column : columns) {
138: appendColumnToList(column);
139: }
140: validate();
141: mainParentPanel.revalidate();
142: scrollToColumn(columns[columns.length - 1], false);
143: }
144:
145: public void clearColumns() {
146: mainParentPanel.removeAll();
147: columnList.clear();
148: splitterList.clear();
149: mainParentPanel.revalidate();
150: mainParentPanel.repaint();
151: }
152:
153: public void removeColumnsAfter(Column column) {
154: if (column == null) {
155: return;
156: }
157:
158: if (!isLastColumn(column)) {
159: // We have to scroll the view (synchronously) before removing
160: // the column, because as soon as we remove it, the table resizes
161: // and revalidates (shrinks).
162: scrollToColumn(getNextColumn(column), true);
163: }
164:
165: // Remove the columns and splitters from the lists and the panel.
166: int loc = columnList.indexOf(column);
167: for (int ii = columnList.size() - 1; ii > loc; ii--) {
168: Column col = columnList.remove(ii);
169: Component comp = col.getComponent();
170: mainParentPanel.remove(comp);
171: }
172: for (int ii = splitterList.size() - 1; ii > loc; ii--) {
173: JSplitterBar bar = splitterList.remove(ii);
174: mainParentPanel.remove(bar);
175: }
176: mainParentPanel.revalidate();
177: mainParentPanel.repaint();
178: }
179:
180: public void scrollToColumn(final Column column, boolean synchronous) {
181: if (column == null) {
182: return;
183: }
184: if (synchronous) {
185: if (!EventQueue.isDispatchThread()) {
186: try {
187: // Invoke ourselves immediately on the event thread
188: EventQueue.invokeAndWait(new Runnable() {
189: public void run() {
190: scrollToColumn(column);
191: }
192: });
193: } catch (Exception e) {
194: return;
195: }
196: } else {
197: // This is the event thread. Invoke the actual scrolling code.
198: scrollToColumn(column);
199: }
200: } else {
201: // Return now and let the invokeLater() do the scrolling
202: EventQueue.invokeLater(new Runnable() {
203: public void run() {
204: // Invoke ourselves later
205: scrollToColumn(column);
206: }
207: });
208: }
209: }
210:
211: /**
212: * Scrolls the viewport to make the specified column the last column
213: * visible. This method must be invoked on the AWT/Swing thread.
214: *
215: * @throws IllegalStateException
216: * if this method is invoked on a non-AWT thread.
217: */
218: protected void scrollToColumn(Column column) {
219: if (!EventQueue.isDispatchThread()) {
220: throw new IllegalStateException("This method can only be "
221: + "invoked on the AWT event processing thread");
222: }
223:
224: // If this column isn't in the table, return immediately.
225: // Likewise, don't scroll if there is only one column.
226: // Also don't scroll if next column is the last one, See IZ100119.
227: int columnIndex = columnList.indexOf(column);
228: if ((columnIndex == -1) || (columnList.size() <= 1)
229: || (columnList.size() - 2 == columnIndex)) {
230: return;
231: }
232:
233: // This is the bounds of the column: the x,y is the coordinate
234: // of the left side of the column relative to the left side of
235: // the viewport, and width, height is the size of the column.
236: Rectangle viewBounds = column.getComponent().getBounds();
237: viewBounds.width += 5;
238:
239: // This is the bounds of the viewport
240: JViewport viewport = scrollPane.getViewport();
241: Rectangle viewportBounds = viewport.getViewRect();
242:
243: // Calculate the distance we need to move the viewport
244: final int DELTA = (int) ((viewportBounds.getX() + viewportBounds
245: .getWidth()) - (viewBounds.getX() + viewBounds
246: .getWidth()));
247: if (DELTA == 0) {
248: return;
249: }
250:
251: // Calculate the number of columns we need to move so we can have
252: // a basically constant per-column scroll rate.
253: // Get the number of columns between the specified column and the
254: // last showing column. We check a point relative (-10,10) to the
255: // upper-rightmost point of the viewport so that we don't
256: // accidentally hit any non-column JTable pixels.
257: int deltaColumns = Math.abs(lastShowingColumnIndex()
258: - columnIndex);
259:
260: Point position = viewport.getViewPosition();
261:
262: final int STEPS = 5 * (deltaColumns == 0 ? 1 : deltaColumns);
263: final int INCREMENT = DELTA / STEPS;
264:
265: for (int step = 0; step < STEPS; step++) {
266: int newX = (int) position.getX() - INCREMENT;
267: if (newX <= 0) {
268: break;
269: }
270: // Add a fudge factor of 1 pixel to insulate against roundoff
271: // errors that cause the view not to scroll fully to the right
272: newX += 1;
273: // //Fix for IZ
274: // try {
275: // // Pause briefly to perform the synchronous animation.
276: // // Using a Timer simply will not work, as this method
277: // // must return only after the animation is complete.
278: // Thread.currentThread().sleep(SCROLL_DELAY);
279: // } catch (InterruptedException ie) {
280: // // Do nothing
281: // }
282:
283: position = new Point(newX, (int) position.getY());
284: viewport.setViewPosition(position);
285: }
286: }
287:
288: /**
289: * Indicates if the given Column is the last one in the view.
290: *
291: * @return true if last column, false otherwise.
292: */
293: protected boolean isLastColumn(Column column) {
294: if (column == null) {
295: return false;
296: }
297: return columnList.indexOf(column) == columnList.size() - 1;
298:
299: }
300:
301: public int getColumnCount() {
302: return columnList.size();
303: }
304:
305: public Column getFirstColumn() {
306: if (columnList.size() > 0) {
307: return columnList.get(0);
308: } else {
309: return null;
310: }
311: }
312:
313: public Column getNextColumn(Column column) {
314: if (column == null) {
315: return null;
316: }
317: if (isLastColumn(column)) {
318: return null;
319: }
320: return columnList.get(columnList.indexOf(column) + 1);
321: }
322:
323: @Override
324: public void requestFocus() {
325: super .requestFocus();
326: int index = lastShowingColumnIndex();
327: if (index > -1) {
328: Column column = columnList.get(index);
329: column.getComponent().requestFocus();
330: }
331: }
332:
333: @Override
334: public boolean requestFocusInWindow() {
335: boolean retVal = super .requestFocusInWindow();
336: int index = lastShowingColumnIndex();
337: if (index > -1) {
338: Column column = columnList.get(index);
339: return column.getComponent().requestFocusInWindow();
340: }
341: return retVal;
342: }
343:
344: /**
345: * Determine the index of the right-most visible column.
346: *
347: * @return index of last visible column (-1 if no columns).
348: */
349: private int lastShowingColumnIndex() {
350: int index = -1;
351: for (int ii = columnList.size() - 1; ii > -1; ii--) {
352: if (columnList.get(ii).getComponent().isShowing()) {
353: index = ii;
354: break;
355: }
356: }
357: return index;
358: }
359:
360: /**
361: * Convert a point in view coordinates to the closest index of the
362: * column at that location.
363: *
364: * @param location the coordinates of the column, relative to the
365: * scrollpane viewport.
366: * @param direction less than zero for left, greater than zero for right.
367: * @return the index of the column at the given location, or -1.
368: */
369: private int locationToIndex(Point location, int direction) {
370: int index = -1;
371: Component comp = mainParentPanel.getComponentAt(location);
372: if (comp instanceof JSplitterBar) {
373: // The columns and splitters have a 1:1 relationship, so the
374: // index into the splitter list is equal to the index into
375: // the column list for the splitter's paired column.
376: index = splitterList.indexOf(comp);
377: if (direction > 0) {
378: // Moving to the right, favor the column to the right
379: // of the splitter.
380: index++;
381: }
382: } else {
383: Component[] comps = mainParentPanel.getComponents();
384: // Components consist of column/splitter pairs.
385: for (int ii = 0; ii < comps.length; ii += 2) {
386: if (comps[ii] == comp) {
387: index = ii / 2;
388: break;
389: }
390: }
391: }
392: return index;
393: }
394:
395: public int getColumnIndex(Column column) {
396: return columnList.indexOf(column);
397: }
398:
399: public void addNotify() {
400: super .addNotify();
401: Container parent = getParent();
402: assert !(parent instanceof JViewport) : "BasicColumnView has its own scrollpane. "
403: + "Do not place BasicColumnView in a scrollpane.";
404: }
405:
406: /**
407: * Panel that manages the scrolling behavior of the column view.
408: */
409: private class MainPanel extends JPanel implements Scrollable {
410: /** silence compiler warnings */
411: private static final long serialVersionUID = 1L;
412:
413: public Dimension getPreferredScrollableViewportSize() {
414: return getPreferredSize();
415: }
416:
417: public int getScrollableBlockIncrement(Rectangle visibleRect,
418: int orientation, int direction) {
419: // If all else fails, default to the view width.
420: int inc = visibleRect.width;
421: if (direction > 0) {
422: // Scroll to the right.
423: int last = locationToIndex(new Point(visibleRect.x
424: + visibleRect.width - 1, visibleRect.y),
425: direction);
426: if (last >= 0 && last < columnList.size()) {
427: Column col = columnList.get(last);
428: Rectangle lastRect = col.getComponent().getBounds();
429: if (lastRect != null) {
430: inc = lastRect.x - visibleRect.x;
431: if (inc < 0) {
432: inc += lastRect.width;
433: } else if (inc == 0
434: && last < columnList.size() - 1) {
435: inc = lastRect.width;
436: }
437: }
438: }
439: } else {
440: // Scroll to the left.
441: int first = locationToIndex(new Point(visibleRect.x
442: - visibleRect.width, visibleRect.y), direction);
443: if (first >= 0 && first < columnList.size()) {
444: Column col = columnList.get(first);
445: Rectangle firstRect = col.getComponent()
446: .getBounds();
447: if (firstRect != null) {
448: if (firstRect.x < visibleRect.x
449: - visibleRect.width) {
450: if (firstRect.x + firstRect.width >= visibleRect.x) {
451: inc = visibleRect.x - firstRect.x;
452: } else {
453: inc = visibleRect.x - firstRect.x
454: - firstRect.width;
455: }
456: } else {
457: inc = visibleRect.x - firstRect.x;
458: }
459: }
460: }
461: }
462: return inc;
463: }
464:
465: public boolean getScrollableTracksViewportHeight() {
466: return true;
467: }
468:
469: public boolean getScrollableTracksViewportWidth() {
470: if (getParent() instanceof JViewport) {
471: return (getParent().getWidth() > getPreferredSize().width);
472: }
473: return false;
474: }
475:
476: public int getScrollableUnitIncrement(Rectangle visibleRect,
477: int orientation, int direction) {
478: int index = locationToIndex(visibleRect.getLocation(),
479: direction);
480: if (index >= 0 && index < columnList.size()) {
481: Column col = columnList.get(index);
482: Rectangle bounds = col.getComponent().getBounds();
483: if (bounds != null) {
484: if (bounds.x != visibleRect.x) {
485: if (direction < 0) {
486: return Math.abs(bounds.x - visibleRect.x);
487: }
488: return bounds.width + bounds.x - visibleRect.x;
489: }
490: // Need to compensate for the width of the splitter.
491: // They are all same width, so first one is sufficient.
492: JSplitterBar bar = splitterList.get(0);
493: return bounds.width + bar.getWidth();
494: }
495: }
496: // If all else fails, return the default value.
497: return 1;
498: }
499: }
500:
501: /** This method is called from within the constructor to
502: * initialize the form.
503: * WARNING: Do NOT modify this code. The content of this method is
504: * always regenerated by the Form Editor.
505: */
506: // <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:initComponents
507: private void initComponents() {
508: scrollPane = new javax.swing.JScrollPane();
509:
510: setLayout(new java.awt.BorderLayout());
511:
512: setBackground(java.awt.Color.white);
513: scrollPane.setBackground(java.awt.Color.white);
514: scrollPane.setBorder(null);
515: scrollPane
516: .setVerticalScrollBarPolicy(javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER);
517: add(scrollPane, java.awt.BorderLayout.CENTER);
518:
519: }// </editor-fold>//GEN-END:initComponents
520:
521: // Variables declaration - do not modify//GEN-BEGIN:variables
522: private javax.swing.JScrollPane scrollPane;
523: // End of variables declaration//GEN-END:variables
524: }
|