001: /*
002: * Javu WingS - Lightweight Java Component Set
003: * Copyright (c) 2005-2007 Krzysztof A. Sadlocha
004: * e-mail: ksadlocha@programics.com
005: *
006: * This library is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation; either
009: * version 2.1 of the License, or (at your option) any later version.
010: *
011: * This library is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: *
016: * You should have received a copy of the GNU Lesser General Public
017: * License along with this library; if not, write to the Free Software
018: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
019: */
020:
021: package com.javujavu.javux.wings;
022:
023: import java.awt.BorderLayout;
024: import java.awt.CardLayout;
025: import java.awt.Component;
026: import java.awt.Dimension;
027: import java.awt.Graphics;
028: import java.awt.Insets;
029: import java.awt.ItemSelectable;
030: import java.awt.Point;
031: import java.awt.Rectangle;
032: import java.awt.event.ActionEvent;
033: import java.awt.event.ItemEvent;
034: import java.awt.event.KeyEvent;
035: import java.awt.event.MouseEvent;
036: import java.util.Vector;
037: import com.javujavu.javux.wings.item.ItemRenderer;
038:
039: /**
040: * This class lets the user switch between a group of components by
041: * clicking on a tab with a given title label.<br>
042: * <br>
043: * <b>This class is thread safe.</b>
044: **/
045: public class WingTabbedPane extends WingComponent implements
046: ItemSelectable {
047: protected static class Tab {
048: Object title;
049: Object longTitle;
050: Object tooltip;
051: WingComponent button;
052:
053: String id;
054: Component component;
055:
056: int width;
057: int buttonWidth;
058: }
059:
060: private/*final*/Vector tabs;
061: private/*final*/WingPanel cardPanel;
062: private/*final*/Bar bar;
063:
064: private int selected;
065: private boolean showSelected = true;
066:
067: public WingTabbedPane() {
068: setLayout(new BorderLayout());
069: tabs = new Vector();
070: cardPanel = new WingPanel(new CardLayout());
071: this .add(cardPanel, BorderLayout.CENTER);
072: bar = new Bar();
073: this .add(bar, BorderLayout.NORTH);
074: }
075:
076: public void setStyleId(String styleId) {
077: super .setStyleId(styleId);
078: bar.setStyleId(styleId);
079: }
080:
081: public void loadSkin() {
082: stNormal = WingSkin.getStyle(styleId, "tab.pane", NORMAL, null);
083: stDisabled = WingSkin.getStyle(styleId, "tab.pane", DISABLED,
084: stNormal);
085: }
086:
087: /**
088: * <br><br><strong>This method acquire TreeLock</strong>
089: */
090: public void setButton(WingComponent button) {
091: bar.setButton(button);
092: }
093:
094: /**
095: * <br><br><strong>This method acquire TreeLock</strong>
096: */
097: public void addTab(Object title, Component component) {
098: addTab(title, null, component, null);
099: }
100:
101: /**
102: * <br><br><strong>This method acquire TreeLock</strong>
103: */
104: public void addTab(Object title, Object longTitle,
105: Component component, WingComponent button) {
106: insertTab(-1, title, longTitle, component, button);
107: }
108:
109: /**
110: * <br><br><strong>This method acquire TreeLock</strong>
111: */
112: public void insertTab(int index, Object title, Object longTitle,
113: Component component, WingComponent button) {
114: Tab t = new Tab();
115:
116: t.title = title;
117: t.longTitle = longTitle;
118: t.component = component;
119: t.button = button;
120: t.id = "tab" + t.hashCode();
121:
122: synchronized (getTreeLock()) {
123: synchronized (this ) {
124: if (index < 0 || index > tabs.size())
125: index = tabs.size();
126: tabs.insertElementAt(t, index);
127: if (tabs.size() == 1)
128: selected = 0;
129:
130: cardPanel.add(t.component, t.id);
131: if (button != null)
132: bar.add(button);
133: bar.revalidateAndRepaint();
134: }
135: }
136: }
137:
138: /**
139: * <br><br><strong>This method acquire TreeLock</strong>
140: */
141: public void removeTab(Component component) {
142: synchronized (getTreeLock()) {
143: synchronized (this ) {
144: int index = indexOfComponent(component);
145: if (index == -1)
146: return;
147: Tab t = (Tab) tabs.elementAt(index);
148: tabs.removeElementAt(index);
149:
150: cardPanel.remove(t.component);
151: if (t.button != null)
152: bar.remove(t.button);
153:
154: if (index < selected
155: || (index == selected && index == tabs.size())) {
156: setSelectedIndex(selected - 1);
157: } else if (index == selected) {
158: selected = -1; //force change
159: setSelectedIndex(index);
160: }
161: bar.revalidateAndRepaint();
162: }
163: }
164: }
165:
166: public synchronized void setTitleAt(int index, Object title,
167: Object longTitle) {
168: if (index < 0 || index >= tabs.size())
169: return;
170: Tab t = (Tab) tabs.elementAt(index);
171: t.title = title;
172: t.longTitle = longTitle;
173: bar.revalidateAndRepaint();
174: }
175:
176: public synchronized void setTitleFor(Component component,
177: Object title, Object longTitle) {
178: setTitleAt(indexOfComponent(component), title, longTitle);
179: }
180:
181: public void setTooltipAt(int index, Object tooltip) {
182: if (index < 0 || index >= tabs.size())
183: return;
184: ((Tab) tabs.elementAt(index)).tooltip = tooltip;
185: }
186:
187: public void setTooltip(Object tooltip) {
188: bar.setTooltip(tooltip);
189: bar.left.setTooltip(tooltip);
190: bar.right.setTooltip(tooltip);
191: bar.list.setTooltip(tooltip);
192: }
193:
194: /**
195: * <br><br><strong>This method acquire TreeLock</strong>
196: */
197: protected void wingProcessKeyEvent(KeyEvent e,
198: WingComponent redirecting) {
199: int key;
200: if (e.getID() == KeyEvent.KEY_PRESSED
201: && ((key = e.getKeyCode()) == KeyEvent.VK_PAGE_UP || key == KeyEvent.VK_PAGE_DOWN)
202: && e.getModifiers() == KeyEvent.CTRL_MASK) {
203: synchronized (getTreeLock()) {
204: synchronized (this ) {
205: if (tabs.size() > 0) {
206: int index = selected;
207: index = (index + tabs.size() + ((key == KeyEvent.VK_PAGE_UP) ? -1
208: : +1))
209: % tabs.size();
210: setSelectedIndex(index);
211: Component c = getSelectedComponent();
212: if (c != null)
213: c.transferFocus();
214: e.consume();
215: }
216: }
217: }
218: }
219: super .wingProcessKeyEvent(e, redirecting);
220: }
221:
222: public int getTabCount() {
223: return tabs.size();
224: }
225:
226: public synchronized int indexOfComponent(Component component) {
227: for (int i = 0; i < tabs.size(); i++) {
228: if (((Tab) tabs.elementAt(i)).component == component)
229: return i;
230: }
231: return -1;
232: }
233:
234: /**
235: * <br><br><strong>This method acquire TreeLock</strong>
236: */
237: public void setSelectedComponent(Component component) {
238: setSelectedComponent(component, false);
239: }
240:
241: /**
242: * <br><br><strong>This method acquire TreeLock</strong>
243: */
244: private void setSelectedComponent(Component component,
245: boolean notify) {
246: setSelected(0, component, notify);
247: }
248:
249: /**
250: * <br><br><strong>This method acquire TreeLock</strong>
251: */
252: public void setSelectedIndex(int index) {
253: setSelected(index, null, false);
254: }
255:
256: /**
257: * <br><br><strong>This method acquire TreeLock</strong>
258: */
259: private void setSelected(int index, Component component,
260: boolean notify) {
261: Tab selTab;
262: synchronized (getTreeLock()) {
263: synchronized (this ) {
264: if (component != null)
265: index = indexOfComponent(component);
266: if (index == selected)
267: return;
268: if (index < -1 || index >= tabs.size())
269: index = -1;
270: selected = index;
271: showSelected = true;
272: bar.revalidateAndRepaint();
273: if (index == -1)
274: return;
275: selTab = (Tab) tabs.elementAt(index);
276: ((CardLayout) cardPanel.getLayout()).show(cardPanel,
277: selTab.id);
278:
279: }
280: }
281: if (notify)
282: wingProcessItemEvent(new ItemEvent(this ,
283: ItemEvent.ITEM_STATE_CHANGED, selTab.component,
284: ItemEvent.SELECTED));
285: }
286:
287: public int getSelectedIndex() {
288: return selected;
289: }
290:
291: public synchronized Component getSelectedComponent() {
292: if (selected == -1)
293: return null;
294: return ((Tab) tabs.elementAt(selected)).component;
295: }
296:
297: public Object[] getSelectedObjects() {
298: Object o = getSelectedComponent();
299: Object[] r;
300: if (o != null) {
301: r = new Object[1];
302: r[0] = o;
303: } else
304: r = new Object[0];
305: return r;
306: }
307:
308: private class Bar extends WingComponent {
309:
310: protected Style stTabNormal;
311: protected Style stTabSelected;
312: protected Style stTabHover;
313: protected Style stTabTail;
314: private int coverLeft;
315: private int coverRight;
316:
317: private final WingButton left;
318: private final WingButton list;
319: private final WingButton right;
320: private WingComponent button;
321: private int hover = -1;
322: private int scrollTab;
323:
324: public Bar() {
325: left = new WingButton();
326: list = new WingButton();
327: right = new WingButton();
328: left.setWingFocusable(false);
329: list.setWingFocusable(false);
330: right.setWingFocusable(false);
331: left.setRepeat(true);
332: list.setFastAction(true);
333: right.setRepeat(true);
334: this .add(left);
335: this .add(list);
336: this .add(right);
337: setStyleId(null);
338: }
339:
340: public void setStyleId(String styleId) {
341: super .setStyleId(styleId);
342: left.setStyleId(WingSkin.catKeys(styleId, "left.tab"));
343: list.setStyleId(WingSkin.catKeys(styleId, "list.tab"));
344: right.setStyleId(WingSkin.catKeys(styleId, "right.tab"));
345: }
346:
347: public void loadSkin() {
348: String idb = WingSkin.catKeys(styleId, "tab.bar");
349: stNormal = WingSkin.getStyle(idb, null, NORMAL, null);
350: stDisabled = WingSkin.getStyle(idb, null, DISABLED,
351: stNormal);
352:
353: String idt = WingSkin.catKeys(styleId, "tab");
354: stTabNormal = WingSkin
355: .getStyle(idt, null, NORMAL, stNormal);
356: stTabHover = WingSkin.getStyle(idt, null, HOVER,
357: stTabNormal);
358: stTabSelected = WingSkin.getStyle(idt, null, SELECTED,
359: stTabNormal);
360: stTabTail = WingSkin.getStyle(idt, "tail", null);
361:
362: coverLeft = WingSkin.getInteger(idt, "cover.left", 0);
363: coverRight = WingSkin.getInteger(idt, "cover.right", 0);
364: }
365:
366: private Style getItemStyle(int i) {
367: return (i == selected) ? stTabSelected
368: : (i == hover && i >= 0) ? stTabHover : stTabNormal;
369: }
370:
371: public Object getTooltipAt(int x, int y) {
372: synchronized (WingTabbedPane.this ) {
373: int t = tabAtPoint(new Point(x, y));
374: if (t != -1) {
375: Object r = ((Tab) tabs.elementAt(t)).tooltip;
376: if (r != null)
377: return r;
378: r = ((Tab) tabs.elementAt(t)).longTitle;
379: if (r != null)
380: return r;
381: }
382: }
383: return super .getTooltipAt(x, y);
384: }
385:
386: /**
387: * <br><br><strong>This method acquire TreeLock</strong>
388: */
389: public void setButton(WingComponent button) {
390: synchronized (getTreeLock()) {
391: if (this .button != null)
392: this .remove(this .button);
393: this .button = button;
394: if (button != null)
395: this .add(button, 2);
396: }
397: revalidateAndRepaint();
398: }
399:
400: /**
401: * <br><br><strong>This method acquire TreeLock</strong>
402: */
403: public Dimension getPreferredSize() {
404: Dimension prefSize = wingPrefSize, d;
405: if (prefSize == null) {
406: Tab t;
407: synchronized (getTreeLock()) {
408: synchronized (WingTabbedPane.this ) {
409: prefSize = new Dimension();
410: for (int i = 0; i < tabs.size(); i++) {
411: t = (Tab) tabs.elementAt(i);
412:
413: d = getRenderer().getItemSize(t.title,
414: this , getItemStyle(i), null);
415: if (d.height > prefSize.height)
416: prefSize.height = d.height;
417: t.width = d.width;
418: if (t.button != null) {
419: d = t.button.getPreferredSize();
420: t.buttonWidth = stTabNormal.gap
421: + d.width;
422: t.width += t.buttonWidth;
423: if (d.height > prefSize.height)
424: prefSize.height = d.height;
425: }
426: prefSize.width += t.width;
427: }
428: if (button != null && button.isVisible()) {
429: d = button.getPreferredSize();
430: prefSize.width += d.width;
431: if (d.height > prefSize.height)
432: prefSize.height = d.height;
433: }
434: wingPrefSize = prefSize;
435: }
436: }
437: }
438: return prefSize;
439: }
440:
441: /**
442: * <br><br><strong>This method acquire TreeLock</strong>
443: */
444: public void doLayout() {
445: synchronized (WingTabbedPane.this ) {
446: Dimension barSize = getPreferredSize();
447: Dimension size = getSize();
448: Dimension d = size;
449: int w = d.width;
450: if (button != null) {
451: d = button.getPreferredSize();
452: w -= d.width;
453: button.setBounds(w, 0, d.width, d.height);
454: }
455:
456: d = right.getPreferredSize();
457: w -= d.width;
458: right.setBounds(w, 0, d.width, d.height);
459:
460: d = list.getPreferredSize();
461: // w-= d.width;
462: list.setBounds(0, 0, d.width, d.height);
463:
464: d = left.getPreferredSize();
465: w -= d.width;
466: left.setBounds(w, 0, d.width, d.height);
467:
468: if (size.width >= barSize.width) {
469: left.setVisible(false);
470: list.setVisible(false);
471: right.setVisible(false);
472: scrollTab = 0;
473: } else {
474: left.setVisible(true);
475: list.setVisible(true);
476: right.setVisible(true);
477: int i, width = w;
478: Tab t;
479: if (showSelected) {
480: if (scrollTab > selected)
481: scrollTab = selected;
482: else {
483: w = 0;
484: for (i = selected; i >= scrollTab; i--) {
485: t = (Tab) tabs.elementAt(i);
486: w += t.width;
487: if (i < selected && w > width)
488: scrollTab = i + 1;
489: }
490: }
491: }
492: w = 0;
493: for (i = scrollTab; i < tabs.size(); i++) {
494: w += ((Tab) tabs.elementAt(i)).width;
495: }
496: for (i = scrollTab - 1; i >= 0; i--) {
497: int w2 = ((Tab) tabs.elementAt(i)).width;
498: if (w + w2 <= width) {
499: scrollTab = i;
500: w += w2;
501: } else
502: break;
503: }
504: left.setEnabled(scrollTab > 0);
505: right.setEnabled(w > width);
506: }
507: w = 0;
508: for (int i = 0; i < tabs.size(); i++) {
509: Tab t = (Tab) tabs.elementAt(i);
510: if (i >= scrollTab)
511: w += t.width;
512: if (t.button != null) {
513: if (i >= scrollTab && w < size.width) {
514: t.button.setVisible(true);
515: d = t.button.getPreferredSize();
516: Style st = getItemStyle(i);
517: t.button.setBounds(w - st.margin.right
518: - d.width, st.margin.top, d.width,
519: d.height);
520: } else
521: t.button.setVisible(false);
522: }
523: }
524: }
525: }
526:
527: public void wingPaint(Graphics g) {
528: int x, i, swapTab, tabWidth, xt;
529: Tab t;
530: Dimension size = getSize();
531: Rectangle cb = g.getClipBounds();
532: int rightX = (cb != null) ? (cb.x + cb.width) : size.width;
533: int leftX = (cb != null) ? cb.x : 0;
534:
535: synchronized (WingTabbedPane.this ) {
536: if (scrollTab >= tabs.size())
537: scrollTab = tabs.size() - 1;
538: if (scrollTab < 0)
539: scrollTab = 0;
540:
541: swapTab = (coverLeft > 0 && coverRight > 0) ? ((selected >= 0) ? selected
542: : 0)
543: : (coverLeft > 0) ? tabs.size() : 0;
544:
545: x = 0;
546: Style st;
547: ItemRenderer renderer = getRenderer();
548: for (i = scrollTab; i < tabs.size() && x < rightX; i++) {
549: t = (Tab) tabs.elementAt(i);
550: if (i < swapTab) {
551: xt = x;
552: tabWidth = t.width;
553: if (coverLeft > 0)
554: tabWidth += coverLeft;
555:
556: st = getItemStyle(i);
557: Insets margin = st.margin;
558: if (t.button != null) {
559: margin = (Insets) st.margin.clone();
560: margin.right += t.buttonWidth;
561: }
562: renderer.drawItem(g, xt, 0, tabWidth,
563: size.height, t.title, this , st, margin,
564: LEFT, RIGHT, null);
565: }
566: x += t.width;
567: }
568: if (x < rightX) {
569: st = stTabTail;
570: if (st.image != null)
571: st.image.drawImage(g, x, 0, size.width - x,
572: size.height, null);
573: }
574: for (i--; i >= swapTab && x > leftX; i--) {
575: t = (Tab) tabs.elementAt(i);
576: x -= t.width;
577:
578: xt = x;
579: tabWidth = t.width;
580: if (i > swapTab && i > scrollTab && coverRight > 0) {
581: xt -= coverRight;
582: tabWidth += coverRight;
583: }
584:
585: st = getItemStyle(i);
586: Insets margin = st.margin;
587: if (t.button != null) {
588: margin = (Insets) st.margin.clone();
589: margin.right += t.buttonWidth;
590: }
591: renderer.drawItem(g, xt, 0, tabWidth, size.height,
592: t.title, this , st, margin, LEFT, RIGHT,
593: null);
594: }
595: }
596: }
597:
598: private int tabAtPoint(Point p) {
599: synchronized (WingTabbedPane.this ) {
600: Tab t;
601: // getPreferredSize();
602: int px = p.x;
603: for (int i = scrollTab; i < tabs.size(); i++) {
604: t = (Tab) tabs.elementAt(i);
605: if (px < t.width) {
606: return i;
607: }
608: px -= t.width;
609: }
610: }
611: return -1;
612: }
613:
614: /**
615: * <br><br><strong>This method acquire TreeLock</strong>
616: */
617: public void wingProcessActionEvent(ActionEvent e) {
618: Object src = e.getSource(), t;
619: if (src == left || src == right) {
620: synchronized (WingTabbedPane.this ) {
621: int scrollOld = scrollTab;
622: if (src == left)
623: scrollTab--;
624: else
625: scrollTab++;
626: if (scrollTab >= tabs.size())
627: scrollTab = tabs.size() - 1;
628: if (scrollTab < 0)
629: scrollTab = 0;
630: if (scrollTab != scrollOld) {
631: showSelected = false;
632: revalidateAndRepaint();
633: }
634: }
635: } else if (src == list) {
636: Rectangle s = list.getBounds();
637: showList(list, s.x, s.y, s.width, s.height);
638: } else if (src instanceof WingMenuItem
639: && (t = ((WingMenuItem) src).userData) != null
640: && tabs.contains(t)) {
641: setSelectedComponent(((Tab) t).component, true);
642: } else
643: super .wingProcessActionEvent(e);
644: }
645:
646: /**
647: * <br><br><strong>This method acquire TreeLock</strong>
648: */
649: private void showList(WingComponent origin, int x, int y,
650: int width, int height) {
651: WingMenu m = new WingMenu(VERTICAL);
652: synchronized (WingTabbedPane.this ) {
653: for (int i = 0; i < tabs.size(); i++) {
654: Tab t = (Tab) tabs.elementAt(i);
655: WingMenuItem mi = new WingMenuItem(
656: (t.longTitle != null) ? t.longTitle
657: : t.title);
658: mi.userData = t;
659: mi.setTooltip(t.tooltip);
660: m.add(mi);
661: }
662: }
663: m.showPopup(origin, x, y, width, height, HORIZONTAL);
664: }
665:
666: /**
667: * <br><br><strong>This method acquire TreeLock</strong>
668: */
669: protected void wingProcessMouseEvent(MouseEvent e) {
670: int id = e.getID();
671: int modifiers = e.getModifiers();
672: if (id == MouseEvent.MOUSE_PRESSED) {
673: if ((modifiers & MouseEvent.BUTTON1_MASK) != 0) {
674: int t = tabAtPoint(e.getPoint());
675: if (t != -1)
676: setSelected(t, null, true);
677: } else {
678: Point p = e.getPoint();
679: showList(this , p.x, p.y, 0, 0);
680: }
681: } else if (id == MouseEvent.MOUSE_ENTERED) {
682: if ((modifiers & (MouseEvent.BUTTON1_MASK
683: | MouseEvent.BUTTON2_MASK | MouseEvent.BUTTON3_MASK)) == 0) {
684: int t = tabAtPoint(e.getPoint());
685: if (t != -1) {
686: hover = t;
687: repaint();
688: }
689: }
690: } else if (id == MouseEvent.MOUSE_EXITED) {
691: if (hover != -1) {
692: hover = -1;
693: repaint();
694: }
695: } else if (id == MouseEvent.MOUSE_MOVED) {
696: int t = tabAtPoint(e.getPoint());
697: if (t != hover) {
698: hover = t;
699: repaint();
700: }
701: }
702: }
703: }
704: }
|