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: * ToolbarTabDisplayerUI.java
043: *
044: * Created on June 1, 2004, 12:31 AM
045: */
046:
047: package org.netbeans.swing.tabcontrol.plaf;
048:
049: import java.awt.*;
050: import java.awt.event.ActionEvent;
051: import java.awt.event.ActionListener;
052: import java.awt.event.MouseListener;
053: import java.util.Arrays;
054: import javax.swing.*;
055: import javax.swing.border.Border;
056: import javax.swing.border.CompoundBorder;
057: import javax.swing.event.ChangeEvent;
058: import javax.swing.event.ChangeListener;
059: import javax.swing.plaf.ComponentUI;
060: import org.netbeans.swing.tabcontrol.TabDisplayer;
061: import org.openide.awt.HtmlRenderer;
062: import org.openide.util.Utilities;
063:
064: /**
065: * A TabDisplayerUI which uses a JToolBar and JButtons. This look is used
066: * in various places such as the property sheet and component inspector.
067: *
068: * @author Tim Boudreau
069: */
070: public class ToolbarTabDisplayerUI extends AbstractTabDisplayerUI {
071: private JToolBar toolbar = null;
072: private static final Border buttonBorder;
073:
074: static {
075: //Get the HIE requested button border via an ugly hack
076: Border b = (Border) UIManager.get("nb.tabbutton.border"); //NOI18N
077:
078: if (b == null) {
079: JToolBar toolbar = new JToolBar();
080: JButton button = new JButton();
081: toolbar.setRollover(true);
082: toolbar.add(button);
083: b = button.getBorder();
084: toolbar.remove(button);
085: }
086:
087: buttonBorder = b;
088: }
089:
090: /** Creates a new instance of ToolbarTabDisplayerUI */
091: public ToolbarTabDisplayerUI(TabDisplayer disp) {
092: super (disp);
093: }
094:
095: public static ComponentUI createUI(JComponent jc) {
096: return new ToolbarTabDisplayerUI((TabDisplayer) jc);
097: }
098:
099: protected TabLayoutModel createLayoutModel() {
100: //not used
101: return null;
102: }
103:
104: protected void install() {
105: toolbar = new TabToolbar();
106: toolbar.setLayout(new AutoGridLayout());
107: toolbar.setFloatable(false);
108: toolbar.setRollover(true);
109: displayer.setLayout(new BorderLayout());
110: displayer.add(toolbar, BorderLayout.CENTER);
111: if (displayer.getModel() != null
112: && displayer.getModel().size() > 0) {
113: syncButtonsWithModel();
114: }
115: }
116:
117: protected void modelChanged() {
118: if (syncButtonsWithModel()) {
119: if (displayer.getParent() != null) {
120: ((JComponent) displayer.getParent()).revalidate();
121: }
122: }
123: }
124:
125: protected MouseListener createMouseListener() {
126: return null;
127: }
128:
129: private IndexButton findButtonFor(int index) {
130: Component[] c = toolbar.getComponents();
131: for (int i = 0; i < c.length; i++) {
132: if (c[i] instanceof IndexButton
133: && ((IndexButton) c[i]).getIndex() == index) {
134: return (IndexButton) c[i];
135: }
136: }
137: return null;
138: }
139:
140: public void requestAttention(int tab) {
141: //not implemented
142: }
143:
144: public void cancelRequestAttention(int tab) {
145: //not implemented
146: }
147:
148: protected ChangeListener createSelectionListener() {
149: return new ChangeListener() {
150: private int lastKnownSelection = -1;
151:
152: public void stateChanged(ChangeEvent ce) {
153: int selection = selectionModel.getSelectedIndex();
154: if (selection != lastKnownSelection) {
155: if (lastKnownSelection != -1) {
156: IndexButton last = findButtonFor(lastKnownSelection);
157: if (last != null) {
158: last.getModel().setSelected(false);
159: }
160: }
161: if (selection != -1) {
162: IndexButton current = findButtonFor(selection);
163: if (toolbar.getComponentCount() == 0) {
164: syncButtonsWithModel();
165: }
166: if (current != null) {
167: current.getModel().setSelected(true);
168: }
169: }
170: }
171: lastKnownSelection = selection;
172: }
173: };
174: }
175:
176: public Polygon getExactTabIndication(int index) {
177: JToggleButton jb = findButtonFor(index);
178: if (jb != null) {
179: return new EqualPolygon(jb.getBounds());
180: } else {
181: return new EqualPolygon(new Rectangle());
182: }
183: }
184:
185: public Polygon getInsertTabIndication(int index) {
186: return getExactTabIndication(index);
187: }
188:
189: public Rectangle getTabRect(int index, Rectangle destination) {
190: destination.setBounds(findButtonFor(index).getBounds());
191: return destination;
192: }
193:
194: public int tabForCoordinate(Point p) {
195: Point p1 = SwingUtilities.convertPoint(displayer, p, toolbar);
196: Component c = toolbar.getComponentAt(p1);
197: if (c instanceof IndexButton) {
198: return ((IndexButton) c).getIndex();
199: }
200: return -1;
201: }
202:
203: public Dimension getPreferredSize(JComponent c) {
204: return toolbar.getPreferredSize();
205: }
206:
207: public Dimension getMinimumSize(JComponent c) {
208: return toolbar.getMinimumSize();
209: }
210:
211: private boolean syncButtonsWithModel() {
212: assert SwingUtilities.isEventDispatchThread();
213:
214: int expected = displayer.getModel().size();
215: int actual = toolbar.getComponentCount();
216: boolean result = actual != expected;
217: if (result) {
218: if (expected > actual) {
219: for (int i = actual; i < expected; i++) {
220: toolbar.add(new IndexButton());
221: }
222: } else if (expected < actual) {
223: for (int i = expected; i < actual; i++) {
224: toolbar.remove(toolbar.getComponentCount() - 1);
225: }
226: }
227: }
228: int selIdx = selectionModel.getSelectedIndex();
229: if (selIdx != -1) {
230: findButtonFor(selIdx).setSelected(true);
231: }
232: if (result) {
233: displayer.revalidate();
234: displayer.repaint();
235: }
236: return result;
237: }
238:
239: public Icon getButtonIcon(int buttonId, int buttonState) {
240: return null;
241: }
242:
243: private ButtonGroup bg = new ButtonGroup();
244: private static int fontHeight = -1;
245: private static int ascent = -1;
246:
247: /**
248: * A button which will get its content from an index in the datamodel
249: * which corresponds to its index in its parent's component hierarchy.
250: */
251: public final class IndexButton extends JToggleButton implements
252: ActionListener {
253: private String lastKnownText = null;
254:
255: /** Create a new button representing an index in the model. The index is immutable for the life of the
256: * button.
257: */
258: public IndexButton() {
259: addActionListener(this );
260: setFont(displayer.getFont());
261: setFocusable(false);
262: setBorder(buttonBorder);
263: setMargin(new Insets(0, 3, 0, 3));
264: setRolloverEnabled(true);
265: }
266:
267: public void addNotify() {
268: super .addNotify();
269: ToolTipManager.sharedInstance().registerComponent(this );
270: bg.add(this );
271: }
272:
273: public void removeNotify() {
274: super .removeNotify();
275: ToolTipManager.sharedInstance().unregisterComponent(this );
276: bg.remove(this );
277: }
278:
279: /** Accessor for the UI delegate to determine if the tab displayer is currently active */
280: public boolean isActive() {
281: return displayer.isActive();
282: }
283:
284: public String getText() {
285: //so the font height is included in super.getPreferredSize();
286: return " ";
287: }
288:
289: public String doGetText() {
290: int idx = getIndex();
291: if (idx == -1) {
292: //We're being called in the superclass constructor when the UI is
293: //assigned
294: return "";
295: }
296: if (getIndex() < displayer.getModel().size()) {
297: lastKnownText = displayer.getModel().getTab(idx)
298: .getText();
299: } else {
300: return "This tab doesn't exist."; //NOI18N
301: }
302: return lastKnownText;
303: }
304:
305: public Dimension getPreferredSize() {
306: Dimension result = super .getPreferredSize();
307: String s = doGetText();
308: int w = DefaultTabLayoutModel.textWidth(s, getFont());
309: result.width += w;
310: // as we cannot get the button small enough using the margin and border...
311: if (Utilities.isMac()) {
312: // #67128 the -3 heuristics seems to cripple the buttons on macosx. it looks ok otherwise.
313: result.height -= 3;
314: }
315: return result;
316: }
317:
318: public void paintComponent(Graphics g) {
319: super .paintComponent(g);
320: String s = doGetText();
321:
322: Insets ins = getInsets();
323: int x = ins.left;
324: int w = getWidth() - (ins.left + ins.right);
325: int h = getHeight();
326:
327: int txtW = DefaultTabLayoutModel.textWidth(s, getFont());
328: if (txtW < w) {
329: x += (w / 2) - (txtW / 2);
330: }
331:
332: if (fontHeight == -1) {
333: FontMetrics fm = g.getFontMetrics(getFont());
334: fontHeight = fm.getHeight();
335: ascent = fm.getMaxAscent();
336: }
337: int y = ins.top
338: + ascent
339: + (((getHeight() - (ins.top + ins.bottom)) / 2) - (fontHeight / 2));
340:
341: HtmlRenderer.renderString(s, g, x, y, w, h, getFont(),
342: getForeground(), HtmlRenderer.STYLE_TRUNCATE, true);
343: }
344:
345: public String getToolTipText() {
346: return displayer.getModel().getTab(getIndex()).getTooltip();
347: }
348:
349: /** Implementation of ActionListener - sets the selected index in the selection model */
350: public final void actionPerformed(ActionEvent e) {
351: selectionModel.setSelectedIndex(getIndex());
352: }
353:
354: /** Get the index into the data model that this button represents */
355: public int getIndex() {
356: if (getParent() != null) {
357: return Arrays.asList(getParent().getComponents())
358: .indexOf(this );
359: }
360: return -1;
361: }
362:
363: public Icon getIcon() {
364: return null;
365: }
366:
367: /**
368: * Test if the text or icon in the model has changed since the last time <code>getText()</code> or
369: * <code>getIcon()</code> was called. If a change has occured, the button will fire the appropriate
370: * property changes, including preferred size, to ensure the tab displayer is re-laid out correctly.
371: * This method is called when a change happens in the model over the index this button represents.
372: *
373: * @return true if something has changed
374: */
375: final boolean checkChanged() {
376: boolean result = false;
377: String txt = lastKnownText;
378: String nu = doGetText();
379: if (nu != txt) { //Equality compare probably not needed
380: firePropertyChange("text", lastKnownText, doGetText()); //NOI18N
381: result = true;
382: }
383: if (result) {
384: firePropertyChange("preferredSize", null, null); //NOI18N
385: }
386: return result;
387: }
388: }
389:
390: /**
391: * Originally in org.netbeans.form.palette.CategorySelectPanel.
392: *
393: * @author Tomas Pavek
394: */
395: static class AutoGridLayout implements LayoutManager {
396:
397: private int h_margin_left = 2; // margin on the left
398: private int h_margin_right = 1; // margin on the right
399: private int v_margin_top = 2; // margin at the top
400: private int v_margin_bottom = 3; // margin at the bottom
401: private int h_gap = 1; // horizontal gap between components
402: private int v_gap = 1; // vertical gap between components
403:
404: public void addLayoutComponent(String name, Component comp) {
405: }
406:
407: public void removeLayoutComponent(Component comp) {
408: }
409:
410: public Dimension preferredLayoutSize(Container parent) {
411: synchronized (parent.getTreeLock()) {
412: int containerWidth = parent.getWidth();
413: int count = parent.getComponentCount();
414:
415: if (containerWidth <= 0 || count == 0) {
416: // compute cumulated width of all components placed on one row
417: int cumulatedWidth = 0;
418: int height = 0;
419: for (int i = 0; i < count; i++) {
420: Dimension size = parent.getComponent(i)
421: .getPreferredSize();
422: cumulatedWidth += size.width;
423: if (i + 1 < count)
424: cumulatedWidth += h_gap;
425: if (size.height > height)
426: height = size.height;
427: }
428: cumulatedWidth += h_margin_left + h_margin_right;
429: height += v_margin_top + v_margin_bottom;
430: return new Dimension(cumulatedWidth, height);
431: }
432:
433: // otherwise the container already has some width set - so we
434: // just compute preferred height for it
435:
436: // get max. component width and height
437: int columnWidth = 0;
438: int rowHeight = 0;
439: for (int i = 0; i < count; i++) {
440: Dimension size = parent.getComponent(i)
441: .getPreferredSize();
442: if (size.width > columnWidth)
443: columnWidth = size.width;
444: if (size.height > rowHeight)
445: rowHeight = size.height;
446: }
447:
448: // compute column count
449: int columnCount = 0;
450: int w = h_margin_left + columnWidth + h_margin_right;
451: do {
452: columnCount++;
453: w += h_gap + columnWidth;
454: } while (w <= containerWidth && columnCount < count);
455:
456: // compute row count and preferred height
457: int rowCount = count / columnCount
458: + (count % columnCount > 0 ? 1 : 0);
459: int prefHeight = v_margin_top + rowCount * rowHeight
460: + (rowCount - 1) * v_gap + v_margin_bottom;
461:
462: Dimension result = new Dimension(containerWidth,
463: prefHeight);
464: return result;
465: }
466: }
467:
468: public Dimension minimumLayoutSize(Container parent) {
469: return new Dimension(h_margin_left + h_margin_right,
470: v_margin_top + v_margin_bottom);
471: }
472:
473: public void layoutContainer(Container parent) {
474: synchronized (parent.getTreeLock()) {
475: int count = parent.getComponentCount();
476: if (count == 0)
477: return;
478:
479: // get max. component width and height
480: int columnWidth = 0;
481: int rowHeight = 0;
482: for (int i = 0; i < count; i++) {
483: Dimension size = parent.getComponent(i)
484: .getPreferredSize();
485: if (size.width > columnWidth)
486: columnWidth = size.width;
487: if (size.height > rowHeight)
488: rowHeight = size.height;
489: }
490:
491: // compute column count
492: int containerWidth = parent.getWidth();
493: int columnCount = 0;
494: int w = h_margin_left + columnWidth + h_margin_right;
495: do {
496: columnCount++;
497: w += h_gap + columnWidth;
498: } while (w <= containerWidth && columnCount < count);
499:
500: // adjust layout matrix - balance number of columns according
501: // to last row
502: if (count % columnCount > 0) {
503: int roundedRowCount = count / columnCount;
504: int lastRowEmpty = columnCount - count
505: % columnCount;
506: if (lastRowEmpty > roundedRowCount)
507: columnCount -= lastRowEmpty
508: / (roundedRowCount + 1);
509: }
510:
511: // adjust column width
512: if (count > columnCount)
513: columnWidth = (containerWidth - h_margin_left
514: - h_margin_right - (columnCount - 1)
515: * h_gap)
516: / columnCount;
517: if (columnWidth < 0)
518: columnWidth = 0;
519:
520: // layout the components
521: for (int i = 0, col = 0, row = 0; i < count; i++) {
522: parent.getComponent(i)
523: .setBounds(
524: h_margin_left + col
525: * (columnWidth + h_gap),
526: v_margin_top + row
527: * (rowHeight + v_gap),
528: columnWidth, rowHeight);
529: if (++col >= columnCount) {
530: col = 0;
531: row++;
532: }
533: }
534: }
535: }
536: }
537:
538: static class TabToolbar extends JToolBar {
539: public void paintComponent(Graphics g) {
540: super .paintComponent(g);
541:
542: Color color = g.getColor();
543:
544: g.setColor(UIManager.getColor("controlLtHighlight")); // NOI18N
545: g.drawLine(0, 0, getWidth(), 0);
546: g.drawLine(0, 0, 0, getHeight() - 1);
547: g.setColor(UIManager.getColor("controlShadow")); // NOI18N
548: g.drawLine(0, getHeight() - 1, getWidth(), getHeight() - 1);
549:
550: g.setColor(color);
551: }
552: }
553: }
|