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: package org.netbeans.swing.tabcontrol.plaf;
043:
044: import java.awt.Graphics;
045: import java.awt.Graphics2D;
046: import java.awt.GraphicsConfiguration;
047: import java.awt.GraphicsEnvironment;
048: import java.awt.Image;
049: import java.awt.Insets;
050: import java.awt.Point;
051: import java.awt.Polygon;
052: import java.awt.Rectangle;
053: import java.awt.Shape;
054: import java.awt.event.MouseEvent;
055: import java.awt.event.MouseListener;
056: import java.awt.event.MouseMotionListener;
057: import java.awt.event.MouseWheelEvent;
058: import java.awt.event.MouseWheelListener;
059: import java.awt.geom.Area;
060: import java.awt.image.BufferedImage;
061: import java.beans.PropertyChangeListener;
062: import javax.swing.JComponent;
063: import javax.swing.JLabel;
064: import javax.swing.SwingUtilities;
065: import javax.swing.event.ChangeEvent;
066: import javax.swing.event.ChangeListener;
067: import javax.swing.event.ListDataEvent;
068: import org.netbeans.swing.tabcontrol.TabData;
069: import org.netbeans.swing.tabcontrol.TabDisplayer;
070: import org.netbeans.swing.tabcontrol.event.ComplexListDataEvent;
071:
072: /**
073: * Base class for tab displayer UIs which use cell renderers to display tabs.
074: * This class does not contain much logic itself, but rather acts to connect events
075: * and data from various objects relating to the tab displayer, which it creates and
076: * installs. Basically, the things that are involved are:
077: * <ul>
078: * <li>A layout model ({@link TabLayoutModel}) - A data model providing the positions and sizes of tabs</li>
079: * <li>A state model ({@link TabState}) - A data model which tracks state data (selected, pressed, etc.)
080: * for each tab, and can be queried when a tab is painted to determine how that should be done.</li>
081: * <li>A selection model ({@link javax.swing.SingleSelectionModel}) - Which tracks which tab is selected</li>
082: * <li>The {@link TabDisplayer} component itself</li>
083: * <li>The {@link TabDisplayer}'s data model, which contains the list of tab names, their icons and
084: * tooltips and the user object (or {@link java.awt.Component}) they identify</li>
085: * <li>Assorted listeners on the component and data models, specifically
086: * <ul><li>A mouse listener that tells the state model when a state-affecting event
087: * has happened, such as the mouse entering a tab</li>
088: * <li>A change listener that repaints appropriately when the selection changes</li>
089: * <li>A property change listener to trigger any repaints needed due to property
090: * changes on the displayer component</li>
091: * <li>A component listener to attach and detach listeners when the component is shown/
092: * hidden, and if neccessary, notify the layout model when the component is resized</li>
093: * <li>A default {@link TabCellRenderer}, which is what will actually paint the tabs, and which
094: * is also responsible for providing some miscellaneous data such as the number of
095: * pixels the layout model should add to tab widths to make room for decorations,
096: * etc.</li>
097: * </ul>
098: * </ul>
099: * The usage pattern of this class is similar to other {@link javax.swing.plaf.ComponentUI} subclasses -
100: * {@link javax.swing.plaf.ComponentUI#installUI}
101: * is called via {@link JComponent#updateUI}. <code>installUI</code> initializes protected fields which
102: * subclasses will need, in a well defined way; abstract methods are provided for subclasses to
103: * create these objects (such as the things listed above), and convenience implementations of some
104: * are provided. <strong>Under no circumstances</strong> should subclasses modify these protected fields -
105: * due to the circuitousness of the way Swing installs UIs, they cannot be declared final, but should
106: * be treated as read-only.
107: * <p>
108: * The goal of this class is to make it quite easy to implement new appearances
109: * for tabs: To create a new appearance, implement a {@link TabCellRenderer} that can
110: * paint individual tabs as desired. This is made even easier via the
111: * {@link TabPainter} interface - simply create the painting logic needed there. Then
112: * subclass <code>BasicTabDisplayerUI</code> and include any painting logic for the background,
113: * scroll buttons, etc. needed. A good example is {@link AquaEditorTabDisplayerUI}.
114: *
115: */
116: public abstract class BasicTabDisplayerUI extends
117: AbstractTabDisplayerUI {
118: protected TabState tabState = null;
119: private static final boolean swingpainting = Boolean
120: .getBoolean("nb.tabs.swingpainting"); //NOI18N
121:
122: protected TabCellRenderer defaultRenderer = null;
123: protected int repaintPolicy = 0;
124:
125: //A couple rectangles for calculation purposes
126: private Rectangle scratch = new Rectangle();
127: private Rectangle scratch2 = new Rectangle();
128: private Rectangle scratch3 = new Rectangle();
129:
130: private Point lastKnownMouseLocation = new Point();
131:
132: int pixelsToAdd = 0;
133:
134: public BasicTabDisplayerUI(TabDisplayer displayer) {
135: super (displayer);
136: }
137:
138: /**
139: * Overridden to initialize the <code>tabState</code> and <code>defaultRenderer</code>.
140: */
141: protected void install() {
142: super .install();
143: tabState = createTabState();
144: defaultRenderer = createDefaultRenderer();
145: layoutModel.setPadding(defaultRenderer.getPadding());
146: pixelsToAdd = defaultRenderer.getPixelsToAddToSelection();
147: repaintPolicy = createRepaintPolicy();
148: if (displayer.getSelectionModel().getSelectedIndex() != -1) {
149: tabState.setSelected(displayer.getSelectionModel()
150: .getSelectedIndex());
151: tabState.setActive(displayer.isActive());
152: }
153: }
154:
155: protected void uninstall() {
156: tabState = null;
157: defaultRenderer = null;
158: super .uninstall();
159: }
160:
161: /** Used by unit tests */
162: TabState getTabState() {
163: return tabState;
164: }
165:
166: /**
167: * Create a TabState instance. TabState manages the state of tabs - that is, which one
168: * contains the mouse, which one is pressed, and so forth, providing methods such as
169: * <code>setMouseInTab(int tab)</code>. Its getState() method returns a bitmask of
170: * states a tab may have which affect the way it is painted.
171: * <p>
172: * <b>Usage:</b> It is expected that UIs will subclass TabState, to implement the
173: * repaint methods, and possibly override <code>getState(int tab)</code> to mix
174: * additional state bits into the bitmask. For example, scrollable tabs have the
175: * possible states CLIP_LEFT and CLIP_RIGHT; BasicScrollingTabDisplayerUI's
176: * implementation of this determines these states by consulting its layout model, and
177: * adds them in when appropriate.
178: *
179: * @return An implementation of TabState
180: * @see BasicTabDisplayerUI.BasicTabState
181: * @see BasicScrollingTabDisplayerUI.ScrollingTabState
182: */
183: protected TabState createTabState() {
184: return new BasicTabState();
185: }
186:
187: /**
188: * Create the default cell renderer for this control. If it is desirable to
189: * have more than one renderer, override getTabCellRenderer()
190: */
191: protected abstract TabCellRenderer createDefaultRenderer();
192:
193: /**
194: * Return a set of insets defining the margins into which tabs should not be
195: * painted. Subclasses that want to paint some controls to the right of the
196: * tabs should include space for those controls in these insets. If a
197: * bottom margin under the tabs is to be painted, include that as well.
198: */
199: public abstract Insets getTabAreaInsets();
200:
201: /**
202: * Get the cell renderer for a given tab. The default implementation simply
203: * returns the renderer created by createDefaultRenderer().
204: */
205: protected TabCellRenderer getTabCellRenderer(int tab) {
206: return defaultRenderer;
207: }
208:
209: /**
210: * Set the passed rectangle's bounds to the recangle in which tabs will be
211: * painted; if your look and feel reserves some part of the tab area for its
212: * own painting. The rectangle is determined by what is returned by
213: * getTabAreaInsets() - this is simply a convenience method for finding the
214: * rectange into which tabs will be painted.
215: */
216: protected final void getTabsVisibleArea(Rectangle rect) {
217: Insets ins = getTabAreaInsets();
218: rect.x = ins.left;
219: rect.y = ins.top;
220: rect.width = displayer.getWidth() - ins.right - ins.left;
221: rect.height = displayer.getHeight() - ins.bottom - ins.top;
222: }
223:
224: protected MouseListener createMouseListener() {
225: return new BasicDisplayerMouseListener();
226: }
227:
228: protected PropertyChangeListener createPropertyChangeListener() {
229: return new BasicDisplayerPropertyChangeListener();
230: }
231:
232: public Polygon getExactTabIndication(int index) {
233: Rectangle r = getTabRect(index, scratch);
234: return getTabCellRenderer(index).getTabShape(
235: tabState.getState(index), r);
236: }
237:
238: public Polygon getInsertTabIndication(int index) {
239: Polygon p;
240: if (index == getLastVisibleTab() + 1) {
241: p = (Polygon) getExactTabIndication(index - 1);
242: Rectangle r = getTabRect(index - 1, scratch);
243: p.translate(r.width / 2, 0);
244: } else {
245: p = (Polygon) getExactTabIndication(index);
246: Rectangle r = getTabRect(index, scratch);
247: p.translate(-(r.width / 2), 0);
248: }
249: return p;
250: }
251:
252: public int tabForCoordinate(Point p) {
253: if (displayer.getModel().size() == 0) {
254: return -1;
255: }
256: getTabsVisibleArea(scratch);
257: if (!scratch.contains(p)) {
258: return -1;
259: }
260: return layoutModel.indexOfPoint(p.x, p.y);
261: }
262:
263: public Rectangle getTabRect(int idx, Rectangle rect) {
264: if (rect == null) {
265: rect = new Rectangle();
266: }
267: if (idx < 0 || idx >= displayer.getModel().size()) {
268: rect.x = rect.y = rect.width = rect.height = 0;
269: return rect;
270: }
271: rect.x = layoutModel.getX(idx);
272: rect.y = layoutModel.getY(idx);
273: rect.width = layoutModel.getW(idx);
274: getTabsVisibleArea(scratch3);
275: //XXX for R->L component orientation cannot assume x = 0
276: int maxPos = scratch.x + scratch3.width;
277: if (rect.x > maxPos) {
278: rect.width = 0;
279: } else if (rect.x + rect.width > maxPos) {
280: rect.width = (maxPos - rect.x);
281: }
282: rect.height = layoutModel.getH(idx);
283: getTabsVisibleArea(scratch2);
284: if (rect.y + rect.height > scratch2.y + scratch2.height) {
285: rect.height = (scratch2.y + scratch2.height) - rect.y;
286: }
287: if (rect.x + rect.width > scratch2.x + scratch2.width) {
288: rect.width = (scratch2.x + scratch2.width) - rect.x;
289: }
290: return rect;
291: }
292:
293: public Image createImageOfTab(int index) {
294: TabData td = displayer.getModel().getTab(index);
295:
296: JLabel lbl = new JLabel(td.getText());
297: int width = lbl.getFontMetrics(lbl.getFont()).stringWidth(
298: td.getText());
299: int height = lbl.getFontMetrics(lbl.getFont()).getHeight();
300: width = width + td.getIcon().getIconWidth() + 6;
301: height = Math.max(height, td.getIcon().getIconHeight()) + 5;
302:
303: GraphicsConfiguration config = GraphicsEnvironment
304: .getLocalGraphicsEnvironment().getDefaultScreenDevice()
305: .getDefaultConfiguration();
306:
307: BufferedImage image = config.createCompatibleImage(width,
308: height);
309: Graphics2D g = image.createGraphics();
310: g.setColor(lbl.getForeground());
311: g.setFont(lbl.getFont());
312: td.getIcon().paintIcon(lbl, g, 0, 0);
313: g.drawString(td.getText(), 18, height / 2);
314:
315: return image;
316: }
317:
318: public int dropIndexOfPoint(Point p) {
319: Point p2 = toDropPoint(p);
320: int start = getFirstVisibleTab();
321: int end = getLastVisibleTab();
322: int target;
323: for (target = start; target <= end; target++) {
324: getTabRect(target, scratch);
325: if (scratch.contains(p2)) {
326: if (target == end) {
327: Object orientation = displayer
328: .getClientProperty(TabDisplayer.PROP_ORIENTATION);
329: boolean flip = displayer.getType() == TabDisplayer.TYPE_SLIDING
330: && (orientation == TabDisplayer.ORIENTATION_EAST || orientation == TabDisplayer.ORIENTATION_WEST);
331:
332: if (flip) {
333: if (p2.y > scratch.y + (scratch.height / 2)) {
334: return target + 1;
335: }
336: } else {
337: if (p2.x > scratch.x + (scratch.width / 2)) {
338: return target + 1;
339: }
340: }
341: }
342: return target;
343: }
344: }
345: return -1;
346: }
347:
348: protected boolean isAntialiased() {
349: return ColorUtil.shouldAntialias();
350: }
351:
352: /**
353: * Paints the tab control. Calls paintBackground(), then paints the tabs using
354: * their cell renderers,
355: * then calls paintAfterTabs
356: */
357: public final void paint(Graphics g, JComponent c) {
358: assert c == displayer;
359:
360: ColorUtil.setupAntialiasing(g);
361:
362: boolean showClose = displayer.isShowCloseButton();
363:
364: paintBackground(g);
365: int start = getFirstVisibleTab();
366: if (start == -1 || !displayer.isShowing()) {
367: return;
368: }
369: //Possible to have a repaint called by a mouse-clicked event if close on mouse press
370: int stop = Math.min(getLastVisibleTab(), displayer.getModel()
371: .size() - 1);
372: getTabsVisibleArea(scratch);
373:
374: //System.err.println("paint, clip bounds: " + g.getClipBounds() + " first visible: " + start + " last: " + stop);
375:
376: if (g.hitClip(scratch.x, scratch.y, scratch.width,
377: scratch.height)) {
378: Shape s = g.getClip();
379: try {
380: //Ensure that we will never paint an icon into the controls area
381: //by setting the clipping bounds
382: if (s != null) {
383: //Okay, some clip area is already set. Get the intersection.
384: Area a = new Area(s);
385: a.intersect(new Area(scratch.getBounds2D()));
386: g.setClip(a);
387: } else {
388: //Clip was not set (it's a normal call to repaint() or something
389: //like that). Just set the bounds.
390: g.setClip(scratch.x, scratch.y, scratch.width,
391: scratch.height);
392: }
393:
394: for (int i = start; i <= stop; i++) {
395: getTabRect(i, scratch);
396: if (g.hitClip(scratch.x, scratch.y,
397: scratch.width + 1, scratch.height + 1)) {
398:
399: int state = tabState.getState(i);
400:
401: if ((state & TabState.NOT_ONSCREEN) == 0) {
402: TabCellRenderer ren = getTabCellRenderer(i);
403: ren.setShowCloseButton(showClose);
404:
405: TabData data = displayer.getModel().getTab(
406: i);
407:
408: JComponent renderer = ren
409: .getRendererComponent(data,
410: scratch, state);
411:
412: renderer.setFont(displayer.getFont());
413: //prepareRenderer ( renderer, data, ren.getLastKnownState () );
414: if (swingpainting) {
415: //Conceivable that some L&F may need this, but it generates
416: //lots of useless events - better to do direct painting where
417: //possible
418: SwingUtilities.paintComponent(g,
419: renderer, displayer, scratch);
420: } else {
421: try {
422: g.translate(scratch.x, scratch.y);
423: renderer.setBounds(scratch);
424: renderer.paint(g);
425: } finally {
426: g.translate(-scratch.x, -scratch.y);
427: }
428: }
429: }
430: }
431: }
432: } finally {
433: g.setClip(s);
434: }
435: }
436: paintAfterTabs(g);
437: }
438:
439: /**
440: * Fill in the background of the component prior to painting the tabs. The default
441: * implementation does nothing. If it's just a matter of filling in a background color,
442: * setOpaque (true) on the displayer, and ComponentUI.update() will take care of the rest.
443: */
444: protected void paintBackground(Graphics g) {
445:
446: }
447:
448: /**
449: * Override this method to provide painting of areas outside the tabs
450: * rectangle, such as margins and controls
451: */
452: protected void paintAfterTabs(Graphics g) {
453: //do nothing
454: }
455:
456: /**
457: * Scrollable implementations will override this method to provide the first
458: * visible (even if clipped) tab. The default implementation returns 0 if
459: * there is at least one tab in the data model, or -1 to indicate the model
460: * is completely empty
461: */
462: protected int getFirstVisibleTab() {
463: return displayer.getModel().size() > 0 ? 0 : -1;
464: }
465:
466: /**
467: * Scrollable implementations will override this method to provide the last
468: * visible (even if clipped) tab. The default implementation returns 0 if
469: * there is at least one tab in the data model, or -1 to indicate the model
470: * is completely empty
471: */
472: protected int getLastVisibleTab() {
473: return displayer.getModel().size() - 1;
474: }
475:
476: protected ChangeListener createSelectionListener() {
477: return new BasicSelectionListener();
478: }
479:
480: protected final Point getLastKnownMouseLocation() {
481: return lastKnownMouseLocation;
482: }
483:
484: /**
485: * Convenience method to override for handling mouse wheel events. The
486: * defualt implementation does nothing.
487: */
488: protected void processMouseWheelEvent(MouseWheelEvent e) {
489: //do nothing
490: }
491:
492: protected final void requestAttention(int tab) {
493: tabState.addAlarmTab(tab);
494: }
495:
496: protected final void cancelRequestAttention(int tab) {
497: tabState.removeAlarmTab(tab);
498: }
499:
500: protected void modelChanged() {
501: tabState.clearTransientStates();
502: //DefaultTabSelectionModel automatically updates its selected index when things
503: //are added/removed from the model, so just make sure our state machine stays in
504: //sync
505: int idx = selectionModel.getSelectedIndex();
506: tabState.setSelected(idx);
507: tabState.pruneAlarmTabs(displayer.getModel().size());
508: super .modelChanged();
509: }
510:
511: /**
512: * Create the policy that will determine what types of events trigger a repaint of one or more tabs.
513: * This is a bitmask composed of constants defined in TabState. The default value is
514: * <pre>
515: * TabState.REPAINT_SELECTION_ON_ACTIVATION_CHANGE
516: | TabState.REPAINT_ON_SELECTION_CHANGE
517: | TabState.REPAINT_ON_MOUSE_ENTER_TAB
518: | TabState.REPAINT_ON_MOUSE_ENTER_CLOSE_BUTTON
519: | TabState.REPAINT_ON_MOUSE_PRESSED;
520: *</pre>
521: *
522: *
523: * @return The repaint policy that should be used in conjunction with mouse events to determine when a
524: * repaint is needed.
525: */
526: protected int createRepaintPolicy() {
527: return TabState.REPAINT_SELECTION_ON_ACTIVATION_CHANGE
528: | TabState.REPAINT_ON_SELECTION_CHANGE
529: | TabState.REPAINT_ON_MOUSE_ENTER_TAB
530: | TabState.REPAINT_ON_MOUSE_ENTER_CLOSE_BUTTON
531: | TabState.REPAINT_ON_MOUSE_PRESSED;
532: }
533:
534: /**
535: * @return Rectangle of the tab to be repainted
536: */
537: protected Rectangle getTabRectForRepaint(int tab, Rectangle rect) {
538: return getTabRect(tab, rect);
539: }
540:
541: protected class BasicTabState extends TabState {
542:
543: public int getState(int tab) {
544: if (displayer.getModel().size() == 0) {
545: return TabState.NOT_ONSCREEN;
546: }
547: int result = super .getState(tab);
548: if (tab == 0) {
549: result |= TabState.LEFTMOST;
550: }
551: if (tab == displayer.getModel().size() - 1) {
552: result |= TabState.RIGHTMOST;
553: }
554: return result;
555: }
556:
557: protected void repaintAllTabs() {
558: //XXX would be nicer to just repaint the tabs area,
559: //but we also need to repaint below all the tabs in the
560: //event of activated/deactivated. No actual reason to
561: //repaint the buttons here.
562: displayer.repaint();
563: }
564:
565: public int getRepaintPolicy(int tab) {
566: //Defined in createRepaintPolicy()
567: return repaintPolicy;
568: }
569:
570: protected void repaintTab(int tab) {
571: if (tab == -1 || tab > displayer.getModel().size()) {
572: return;
573: }
574: getTabRectForRepaint(tab, scratch);
575: scratch.y = 0;
576: scratch.height = displayer.getHeight();
577: displayer.repaint(scratch.x, scratch.y, scratch.width,
578: scratch.height);
579: }
580: }
581:
582: protected ModelListener createModelListener() {
583: return new BasicModelListener();
584: }
585:
586: private class BasicDisplayerPropertyChangeListener extends
587: DisplayerPropertyChangeListener {
588:
589: protected void activationChanged() {
590: tabState.setActive(displayer.isActive());
591: }
592: }
593:
594: protected class BasicDisplayerMouseListener implements
595: MouseListener, MouseMotionListener, MouseWheelListener {
596: private int updateMouseLocation(MouseEvent e) {
597: lastKnownMouseLocation.x = e.getX();
598: lastKnownMouseLocation.y = e.getY();
599: return tabForCoordinate(lastKnownMouseLocation);
600: }
601:
602: public void mouseClicked(MouseEvent e) {
603: int idx = updateMouseLocation(e);
604: if (idx == -1) {
605: return;
606: }
607:
608: TabCellRenderer tcr = getTabCellRenderer(idx);
609: getTabRect(idx, scratch);
610: int state = tabState.getState(idx);
611:
612: potentialCommand(idx, e, state, tcr, scratch);
613: }
614:
615: public void mouseDragged(MouseEvent e) {
616: mouseMoved(e);
617: }
618:
619: public void mouseEntered(MouseEvent e) {
620: int idx = updateMouseLocation(e);
621: tabState.setMouseInTabsArea(true);
622: tabState.setContainsMouse(idx);
623: }
624:
625: public void mouseExited(MouseEvent e) {
626: updateMouseLocation(e);
627: tabState.setMouseInTabsArea(false);
628: tabState.setContainsMouse(-1);
629: tabState.setCloseButtonContainsMouse(-1);
630: }
631:
632: public void mouseMoved(MouseEvent e) {
633: int idx = updateMouseLocation(e);
634: tabState.setMouseInTabsArea(true);
635: tabState.setContainsMouse(idx);
636: if (idx != -1) {
637: TabCellRenderer tcr = getTabCellRenderer(idx);
638: getTabRect(idx, scratch);
639: int state = tabState.getState(idx);
640:
641: String s = tcr.getCommandAtPoint(e.getPoint(), state,
642: scratch);
643: if (TabDisplayer.COMMAND_CLOSE == s) {
644: tabState.setCloseButtonContainsMouse(idx);
645: } else {
646: tabState.setCloseButtonContainsMouse(-1);
647: }
648: } else {
649: tabState.setContainsMouse(-1);
650: }
651: }
652:
653: private int lastPressedTab = -1;
654: private long pressTime = -1;
655:
656: public void mousePressed(MouseEvent e) {
657: int idx = updateMouseLocation(e);
658: tabState.setPressed(idx);
659:
660: //One a double click, preserve the tab that was initially clicked, in case
661: //a re-layout happened. We'll pass that to the action.
662: long time = e.getWhen();
663: if (time - pressTime > 200) {
664: lastPressedTab = idx;
665: }
666: pressTime = time;
667: lastPressedTab = idx;
668: if (idx != -1) {
669: TabCellRenderer tcr = getTabCellRenderer(idx);
670: getTabRect(idx, scratch);
671: int state = tabState.getState(idx);
672:
673: //First find the command for the location with the default button -
674: //TabState may trigger a repaint
675: String command = tcr.getCommandAtPoint(e.getPoint(),
676: state, scratch);
677: if (TabDisplayer.COMMAND_CLOSE == command) {
678: tabState.setCloseButtonContainsMouse(idx);
679: tabState.setMousePressedInCloseButton(idx);
680:
681: //We're closing, don't try to maximize this tab if it turns out to be
682: //a double click
683: pressTime = -1;
684: lastPressedTab = -1;
685: }
686:
687: potentialCommand(idx, e, state, tcr, scratch);
688: } else {
689: tabState.setMousePressedInCloseButton(-1); //just in case
690: }
691: }
692:
693: private void potentialCommand(int idx, MouseEvent e, int state,
694: TabCellRenderer tcr, Rectangle bounds) {
695: String command = tcr.getCommandAtPoint(e.getPoint(), state,
696: bounds, e.getButton(), e.getID(), e
697: .getModifiersEx());
698: if (command == null
699: || TabDisplayer.COMMAND_SELECT == command) {
700: if (e.isPopupTrigger()) {
701: displayer.repaint();
702: performCommand(TabDisplayer.COMMAND_POPUP_REQUEST,
703: idx, e);
704: return;
705: } else if (e.getID() == MouseEvent.MOUSE_CLICKED
706: && e.getClickCount() >= 2) {
707: performCommand(TabDisplayer.COMMAND_MAXIMIZE, idx,
708: e);
709: return;
710: }
711: }
712:
713: if (command != null) {
714: performCommand(command,
715: lastPressedTab == -1
716: || lastPressedTab > displayer
717: .getModel().size() ? idx
718: : lastPressedTab, e);
719: }
720: }
721:
722: private void performCommand(String command, int idx,
723: MouseEvent evt) {
724: evt.consume();
725: if (TabDisplayer.COMMAND_SELECT == command) {
726: if (idx != displayer.getSelectionModel()
727: .getSelectedIndex()) {
728: boolean go = shouldPerformAction(command, idx, evt);
729: if (go) {
730: selectionModel.setSelectedIndex(idx);
731: }
732: }
733: } else {
734: boolean should = shouldPerformAction(command, idx, evt)
735: && displayer.isShowCloseButton();
736: if (should) {
737: if (TabDisplayer.COMMAND_CLOSE == command) {
738: displayer.getModel().removeTab(idx);
739: } else if (TabDisplayer.COMMAND_CLOSE_ALL == command) {
740: displayer.getModel().removeTabs(0,
741: displayer.getModel().size());
742: } else if (TabDisplayer.COMMAND_CLOSE_ALL_BUT_THIS == command) {
743: int start;
744: int end;
745: if (idx != displayer.getModel().size() - 1) {
746: start = idx + 1;
747: end = displayer.getModel().size();
748: displayer.getModel().removeTabs(start, end);
749: }
750: if (idx != 0) {
751: start = 0;
752: end = idx;
753: displayer.getModel().removeTabs(start, end);
754: }
755: }
756: }
757: }
758: }
759:
760: public void mouseReleased(MouseEvent e) {
761: int idx = updateMouseLocation(e);
762: if (idx != -1) {
763: TabCellRenderer tcr = getTabCellRenderer(idx);
764: getTabRect(idx, scratch);
765: int state = tabState.getState(idx);
766: if ((state & TabState.PRESSED) != 0
767: && ((state & TabState.CLIP_LEFT) != 0)
768: || (state & TabState.CLIP_RIGHT) != 0) {
769: makeTabVisible(idx);
770: }
771: potentialCommand(idx, e, state, tcr, scratch);
772: }
773: tabState.setMouseInTabsArea(idx != -1);
774: tabState.setPressed(-1);
775: tabState.setMousePressedInCloseButton(-1);
776: }
777:
778: public final void mouseWheelMoved(MouseWheelEvent e) {
779: updateMouseLocation(e);
780: processMouseWheelEvent(e);
781: }
782: }
783:
784: /** A simple selection listener implementation which updates the TabState model
785: * with the new selected index from the selection model when it changes.
786: */
787: protected class BasicSelectionListener implements ChangeListener {
788: public void stateChanged(ChangeEvent e) {
789: assert e.getSource() == selectionModel : "Unknown event source: "
790: + e.getSource();
791: int idx = selectionModel.getSelectedIndex();
792: tabState.setSelected(idx >= 0 ? idx : -1);
793: if (idx >= 0) {
794: makeTabVisible(selectionModel.getSelectedIndex());
795: }
796: }
797: }
798:
799: /**
800: * Listener on data model which will pass modified indices to the
801: * TabState object, so it can update which tab indices are flashing in
802: * "attention" mode, if any.
803: */
804: protected class BasicModelListener extends
805: AbstractTabDisplayerUI.ModelListener {
806: public void contentsChanged(ListDataEvent e) {
807: super .contentsChanged(e);
808: tabState.contentsChanged(e);
809: }
810:
811: public void indicesAdded(ComplexListDataEvent e) {
812: super .indicesAdded(e);
813: tabState.indicesAdded(e);
814: }
815:
816: public void indicesChanged(ComplexListDataEvent e) {
817: tabState.indicesChanged(e);
818: }
819:
820: public void indicesRemoved(ComplexListDataEvent e) {
821: tabState.indicesRemoved(e);
822: }
823:
824: public void intervalAdded(ListDataEvent e) {
825: tabState.intervalAdded(e);
826: }
827:
828: public void intervalRemoved(ListDataEvent e) {
829: tabState.intervalRemoved(e);
830: }
831: }
832: }
|