File: BasicTabDisplayerUI.java

package info (click to toggle)
libnb-platform18-java 8.1%2Bdfsg1-5
  • links: PTS, VCS
  • area: main
  • in suites: stretch
  • size: 85,228 kB
  • ctags: 104,620
  • sloc: java: 705,506; xml: 124,894; ansic: 6,317; sh: 4,043; cpp: 2,492; objc: 288; perl: 277; makefile: 261
file content (889 lines) | stat: -rw-r--r-- 35,440 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 1997-2010 Oracle and/or its affiliates. All rights reserved.
 *
 * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
 * Other names may be trademarks of their respective owners.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common
 * Development and Distribution License("CDDL") (collectively, the
 * "License"). You may not use this file except in compliance with the
 * License. You can obtain a copy of the License at
 * http://www.netbeans.org/cddl-gplv2.html
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
 * specific language governing permissions and limitations under the
 * License.  When distributing the software, include this License Header
 * Notice in each file and include the License file at
 * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the GPL Version 2 section of the License file that
 * accompanied this code. If applicable, add the following below the
 * License Header, with the fields enclosed by brackets [] replaced by
 * your own identifying information:
 * "Portions Copyrighted [year] [name of copyright owner]"
 *
 * Contributor(s):
 *
 * The Original Software is NetBeans. The Initial Developer of the Original
 * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
 * Microsystems, Inc. All Rights Reserved.
 *
 * If you wish your version of this file to be governed by only the CDDL
 * or only the GPL Version 2, indicate your decision by adding
 * "[Contributor] elects to include this software in this distribution
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
 * single choice of license, a recipient has the option to distribute
 * your version of this file under either the CDDL, the GPL Version 2 or
 * to extend the choice of license to its licensees as provided above.
 * However, if you add GPL Version 2 code and therefore, elected the GPL
 * Version 2 license, then the option applies only if the new code is
 * made subject to such option by the copyright holder.
 */

package org.netbeans.swing.tabcontrol.plaf;

import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsEnvironment;
import java.awt.Image;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Polygon;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.awt.geom.Area;
import java.awt.image.BufferedImage;
import java.beans.PropertyChangeListener;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.ListDataEvent;
import org.netbeans.swing.tabcontrol.TabData;
import org.netbeans.swing.tabcontrol.TabDisplayer;
import org.netbeans.swing.tabcontrol.WinsysInfoForTabbedContainer;
import org.netbeans.swing.tabcontrol.event.ComplexListDataEvent;
import org.openide.windows.TopComponent;

/**
 * Base class for tab displayer UIs which use cell renderers to display tabs.
 * This class does not contain much logic itself, but rather acts to connect events
 * and data from various objects relating to the tab displayer, which it creates and
 * installs.  Basically, the things that are involved are:
 * <ul>
 * <li>A layout model ({@link TabLayoutModel}) - A data model providing the positions and sizes of tabs</li>
 * <li>A state model ({@link TabState}) - A data model which tracks state data (selected, pressed, etc.)
 *     for each tab, and can be queried when a tab is painted to determine how that should be done.</li>
 * <li>A selection model ({@link javax.swing.SingleSelectionModel}) - Which tracks which tab is selected</li>
 * <li>The {@link TabDisplayer} component itself</li>
 * <li>The {@link TabDisplayer}'s data model, which contains the list of tab names, their icons and
 *     tooltips and the user object (or {@link java.awt.Component}) they identify</li>
 * <li>Assorted listeners on the component and data models, specifically
 *       <ul><li>A mouse listener that tells the state model when a state-affecting event
 *               has happened, such as the mouse entering a tab</li>
 *           <li>A change listener that repaints appropriately when the selection changes</li>
 *           <li>A property change listener to trigger any repaints needed due to property
 *               changes on the displayer component</li>
 *           <li>A component listener to attach and detach listeners when the component is shown/
 *               hidden, and if neccessary, notify the layout model when the component is resized</li>
 *           <li>A default {@link TabCellRenderer}, which is what will actually paint the tabs, and which
 *               is also responsible for providing some miscellaneous data such as the number of
 *               pixels the layout model should add to tab widths to make room for decorations,
 *               etc.</li>
 *       </ul>
 * </ul>
 * The usage pattern of this class is similar to other {@link javax.swing.plaf.ComponentUI} subclasses -
 * {@link javax.swing.plaf.ComponentUI#installUI}
 * is called via {@link JComponent#updateUI}.  <code>installUI</code> initializes protected fields which
 * subclasses will need, in a well defined way; abstract methods are provided for subclasses to
 * create these objects (such as the things listed above), and convenience implementations of some
 * are provided. <strong>Under no circumstances</strong> should subclasses modify these protected fields -
 * due to the circuitousness of the way Swing installs UIs, they cannot be declared final, but should
 * be treated as read-only.
 * <p>
 * The goal of this class is to make it quite easy to implement new appearances
 * for tabs:  To create a new appearance, implement a {@link TabCellRenderer} that can 
 * paint individual tabs as desired.  This is made even easier via the 
 * {@link TabPainter} interface - simply create the painting logic needed there.  Then
 * subclass <code>BasicTabDisplayerUI</code> and include any painting logic for the background,
 * scroll buttons, etc. needed.  A good example is {@link AquaEditorTabDisplayerUI}.
 *
 */
public abstract class BasicTabDisplayerUI extends AbstractTabDisplayerUI {
    protected TabState tabState = null;
    private static final boolean swingpainting = Boolean.getBoolean(
            "nb.tabs.swingpainting"); //NOI18N

    protected TabCellRenderer defaultRenderer = null;
    protected int repaintPolicy = 0;

    //A couple rectangles for calculation purposes
    private Rectangle scratch = new Rectangle();
    private Rectangle scratch2 = new Rectangle();
    private Rectangle scratch3 = new Rectangle();

    private Point lastKnownMouseLocation = new Point();

    int pixelsToAdd = 0;   
    
    public BasicTabDisplayerUI(TabDisplayer displayer) {
        super(displayer);
    }

    /**
     * Overridden to initialize the <code>tabState</code> and <code>defaultRenderer</code>.
     */
    @Override
    protected void install() {
        super.install();
        tabState = createTabState();
        defaultRenderer = createDefaultRenderer();
        if( null != displayer.getContainerWinsysInfo() ) {
            defaultRenderer.setShowCloseButton( displayer.getContainerWinsysInfo().isTopComponentClosingEnabled() );
        }
        layoutModel.setPadding (defaultRenderer.getPadding());
        pixelsToAdd = defaultRenderer.getPixelsToAddToSelection();
        repaintPolicy = createRepaintPolicy();
        if (displayer.getSelectionModel().getSelectedIndex() != -1) {
            tabState.setSelected(displayer.getSelectionModel().getSelectedIndex());
            tabState.setActive(displayer.isActive());
        }
    }

    @Override
    protected void uninstall() {
        tabState = null;
        defaultRenderer = null;
        super.uninstall();
    }
    
    /** Used by unit tests */
    TabState getTabState() {
        return tabState;
    }

    /**
     * Create a TabState instance.  TabState manages the state of tabs - that is, which one
     * contains the mouse, which one is pressed, and so forth, providing methods such as
     * <code>setMouseInTab(int tab)</code>.  Its getState() method returns a bitmask of
     * states a tab may have which affect the way it is painted.
     * <p>
     * <b>Usage:</b> It is expected that UIs will subclass TabState, to implement the
     * repaint methods, and possibly override <code>getState(int tab)</code> to mix
     * additional state bits into the bitmask.  For example, scrollable tabs have the
     * possible states CLIP_LEFT and CLIP_RIGHT; BasicScrollingTabDisplayerUI's
     * implementation of this determines these states by consulting its layout model, and
     * adds them in when appropriate.
     *
     * @return An implementation of TabState
     * @see BasicTabDisplayerUI.BasicTabState
     * @see BasicScrollingTabDisplayerUI.ScrollingTabState
     */
    protected TabState createTabState() {
        return new BasicTabState();
    }

    /**
     * Create the default cell renderer for this control.  If it is desirable to
     * have more than one renderer, override getTabCellRenderer()
     */
    protected abstract TabCellRenderer createDefaultRenderer();

    /**
     * Return a set of insets defining the margins into which tabs should not be
     * painted.  Subclasses that want to paint some controls to the right of the
     * tabs should include space for those controls in these insets.  If a
     * bottom margin under the tabs is to be painted, include that as well.
     */
    public abstract Insets getTabAreaInsets();

    /**
     * Get the cell renderer for a given tab.  The default implementation simply
     * returns the renderer created by createDefaultRenderer().
     * @param tab
     * @return 
     */
    public TabCellRenderer getTabCellRenderer(int tab) {
        defaultRenderer.setShowCloseButton(displayer.isShowCloseButton());
        if( tab >=0 && tab < displayer.getModel().size() ) {
            TabData data = displayer.getModel().getTab(tab);
            boolean closingEnabled = true;
            if( data.getComponent() instanceof TopComponent ) {
                closingEnabled = displayer.getContainerWinsysInfo().isTopComponentClosingEnabled( (TopComponent)data.getComponent() );
            }

            defaultRenderer.setShowCloseButton(displayer.isShowCloseButton() && closingEnabled);
        }
        return defaultRenderer;
    }

    /**
     * Set the passed rectangle's bounds to the recangle in which tabs will be
     * painted; if your look and feel reserves some part of the tab area for its
     * own painting.  The rectangle is determined by what is returned by
     * getTabAreaInsets() - this is simply a convenience method for finding the
     * rectange into which tabs will be painted.
     */
    protected final void getTabsVisibleArea(Rectangle rect) {
        Insets ins = getTabAreaInsets();
        rect.x = ins.left;
        rect.y = ins.top;
        rect.width = displayer.getWidth() - ins.right - ins.left;
        rect.height = displayer.getHeight() - ins.bottom - ins.top;
    }

    @Override
    protected MouseListener createMouseListener() {
        return new BasicDisplayerMouseListener();
    }

    @Override
    protected PropertyChangeListener createPropertyChangeListener() {
        return new BasicDisplayerPropertyChangeListener();
    }

    @Override
    public Polygon getExactTabIndication(int index) {
        Rectangle r = getTabRect(index, scratch);
        return getTabCellRenderer(index).getTabShape(tabState.getState(index), r);
    }

    @Override
    public Polygon getInsertTabIndication(int index) {
        Polygon p;
        if (index == getLastVisibleTab() + 1) {
            p = (Polygon) getExactTabIndication (index-1);
            Rectangle r = getTabRect(index-1, scratch);
            p.translate(r.width/2, 0);
        } else {
            p = (Polygon) getExactTabIndication (index);
            Rectangle r = getTabRect(index, scratch);
            p.translate(-(r.width/2), 0);
        }
        return p;
    }

    @Override
    public int tabForCoordinate(Point p) {
        if (displayer.getModel().size() == 0) {
            return -1;
        }
        getTabsVisibleArea(scratch);
        if (!scratch.contains(p)) {
            return -1;
        }
        int tabIndex = layoutModel.indexOfPoint(p.x, p.y);
        if( tabIndex >= displayer.getModel().size() )
            tabIndex = -1;
        return tabIndex;
    }

    @Override
    public Rectangle getTabRect(int idx, Rectangle rect) {
        if (rect == null) {
            rect = new Rectangle();
        }
        if (idx < 0 || idx >= displayer.getModel().size()) {
            rect.x = rect.y = rect.width = rect.height = 0;
            return rect;
        }
        rect.x = layoutModel.getX(idx);
        rect.y = layoutModel.getY(idx);
        rect.width = layoutModel.getW(idx);
        getTabsVisibleArea(scratch3);
        //XXX for R->L component orientation cannot assume x = 0
        int maxPos = scratch.x + scratch3.width;
        if (rect.x > maxPos) {
            rect.width = 0;
        } else if (rect.x + rect.width > maxPos) {
            rect.width = (maxPos - rect.x);
        }
        rect.height = layoutModel.getH(idx);
        getTabsVisibleArea(scratch2);
        if (rect.y + rect.height > scratch2.y + scratch2.height) {
            rect.height = (scratch2.y + scratch2.height) - rect.y;
        }
        if (rect.x + rect.width > scratch2.x + scratch2.width) {
            rect.width = (scratch2.x + scratch2.width) - rect.x;
        }
        return rect;
    }

    @Override
    public Image createImageOfTab(int index) {
        TabData td = displayer.getModel().getTab(index);
        
        JLabel lbl = new JLabel(td.getText());
        int width = lbl.getFontMetrics(lbl.getFont()).stringWidth(td.getText());
        int height = lbl.getFontMetrics(lbl.getFont()).getHeight();
        width = width + td.getIcon().getIconWidth() + 6;
        height = Math.max(height, td.getIcon().getIconHeight()) + 5;
        
        GraphicsConfiguration config = GraphicsEnvironment.getLocalGraphicsEnvironment()
                                        .getDefaultScreenDevice().getDefaultConfiguration();
        
        BufferedImage image = config.createCompatibleImage(width, height);
        Graphics2D g = image.createGraphics();
        g.setColor(lbl.getForeground());
        g.setFont(lbl.getFont());
        td.getIcon().paintIcon(lbl, g, 0, 0);
        g.drawString(td.getText(), 18, height / 2);
        
        
        return image;
    }

    @Override
    public int dropIndexOfPoint(Point p) {
        Point p2 = toDropPoint(p);
        int start = getFirstVisibleTab();
        int end = getLastVisibleTab();
        int target;
        for (target = start; target <= end; target ++) {
            getTabRect (target, scratch);
            if (scratch.contains(p2)) {
                if (target == end) {
                    Object orientation = displayer.getClientProperty (TabDisplayer.PROP_ORIENTATION);
                    boolean flip = displayer.getType() == TabDisplayer.TYPE_SLIDING && (
                            orientation == TabDisplayer.ORIENTATION_EAST ||
                            orientation == TabDisplayer.ORIENTATION_WEST);

                    if (flip) {
                        if (p2.y > scratch.y + (scratch.height / 2)) {
                            return target+1;
                        }
                    } else {
                        if (p2.x > scratch.x + (scratch.width / 2)) {
                            return target+1;
                        }
                    }
                }
                return target;
            }
        }
        return -1;
    }

    protected boolean isAntialiased() {
        return ColorUtil.shouldAntialias();
    }

    /**
     * Paints the tab control.  Calls paintBackground(), then paints the tabs using
     * their cell renderers,
     * then calls paintAfterTabs
     */
    @Override
    public final void paint(Graphics g, JComponent c) {
        assert c == displayer;
        
        ColorUtil.setupAntialiasing(g);
        
        paintBackground(g);
        int start = getFirstVisibleTab();
        if (start == -1 || !displayer.isShowing()) {
            return;
        }
        //Possible to have a repaint called by a mouse-clicked event if close on mouse press
        int stop = Math.min(getLastVisibleTab(), displayer.getModel().size() - 1);
        getTabsVisibleArea(scratch);
        
//System.err.println("paint, clip bounds: " + g.getClipBounds() + " first visible: " + start + " last: " + stop);

        if (g.hitClip(scratch.x, scratch.y, scratch.width, scratch.height)) {
            Shape s = g.getClip();
            try {
                //Ensure that we will never paint an icon into the controls area
                //by setting the clipping bounds
                if (s != null) {
                    //Okay, some clip area is already set.  Get the intersection.
                    Area a = new Area(s);
                    a.intersect(new Area(scratch.getBounds2D()));
                    g.setClip(a);
                } else {
                    //Clip was not set (it's a normal call to repaint() or something
                    //like that).  Just set the bounds.
                    g.setClip(scratch.x, scratch.y, scratch.width,
                              scratch.height);
                }


                for (int i = start; i <= stop; i++) {
                    getTabRect(i, scratch);
                    if (g.hitClip(scratch.x, scratch.y, scratch.width + 1,
                                  scratch.height + 1)) {
                                      
                        int state = tabState.getState(i);
                        
                        if ((state & TabState.NOT_ONSCREEN) == 0) {
                            TabCellRenderer ren = getTabCellRenderer(i);
                            
                            TabData data = displayer.getModel().getTab(i);

                            if( isTabBusy( i ) ) {
                                state |= TabState.BUSY;
                            }
                            
                            JComponent renderer = ren.getRendererComponent(
                                    data, scratch, state);
                            
                            renderer.setFont(displayer.getFont());
                            //prepareRenderer ( renderer, data, ren.getLastKnownState () );
                            if (swingpainting) {
                                //Conceivable that some L&F may need this, but it generates
                                //lots of useless events - better to do direct painting where
                                //possible
                                SwingUtilities.paintComponent(g, renderer,
                                                              displayer,
                                                              scratch);
                            } else {
                                try {
                                    g.translate(scratch.x, scratch.y);
                                    renderer.setBounds(scratch);
                                    renderer.paint(g);
                                } finally {
                                    g.translate(-scratch.x, -scratch.y);
                                }
                            }
                        }
                    }
                }
            } finally {
                g.setClip(s);
            }
        }
        paintAfterTabs(g);
    }

    /**
     * Fill in the background of the component prior to painting the tabs.  The default
     * implementation does nothing.  If it's just a matter of filling in a background color,
     * setOpaque (true) on the displayer, and ComponentUI.update() will take care of the rest.
     */
    protected void paintBackground(Graphics g) {

    }

    /**
     * Override this method to provide painting of areas outside the tabs
     * rectangle, such as margins and controls
     */
    protected void paintAfterTabs(Graphics g) {
        //do nothing
    }

    /**
     * Scrollable implementations will override this method to provide the first
     * visible (even if clipped) tab.  The default implementation returns 0 if
     * there is at least one tab in the data model, or -1 to indicate the model
     * is completely empty
     */
    protected int getFirstVisibleTab() {
        return displayer.getModel().size() > 0 ? 0 : -1;
    }

    /**
     * Scrollable implementations will override this method to provide the last
     * visible (even if clipped) tab.  The default implementation returns 0 if
     * there is at least one tab in the data model, or -1 to indicate the model
     * is completely empty
     */
    protected int getLastVisibleTab() {
        return displayer.getModel().size() - 1;
    }

    @Override
    protected ChangeListener createSelectionListener() {
        return new BasicSelectionListener();
    }

    protected final Point getLastKnownMouseLocation() {
        return lastKnownMouseLocation;
    }

    /**
     * Convenience method to override for handling mouse wheel events. The
     * defualt implementation does nothing.
     */
    protected void processMouseWheelEvent(MouseWheelEvent e) {
        //do nothing
    }
    
    @Override
    protected final void requestAttention (int tab) {
        tabState.addAlarmTab(tab);
    }

    @Override
    protected final void cancelRequestAttention (int tab) {
        tabState.removeAlarmTab(tab);
    }

    @Override
    protected final void setAttentionHighlight (int tab, boolean highlight) {
        if( highlight ) {
            tabState.addHighlightTab(tab);
        } else {
            tabState.removeHighlightTab(tab);
        }
    }

    @Override
    protected void modelChanged() {
        tabState.clearTransientStates();
        //DefaultTabSelectionModel automatically updates its selected index when things
        //are added/removed from the model, so just make sure our state machine stays in
        //sync
        int idx = selectionModel.getSelectedIndex();
        tabState.setSelected(idx);
        tabState.pruneTabs(displayer.getModel().size());
        super.modelChanged();
    }

    /**
     * Create the policy that will determine what types of events trigger a repaint of one or more tabs.
     * This is a bitmask composed of constants defined in TabState. The default value is
     * <pre>
     *  TabState.REPAINT_SELECTION_ON_ACTIVATION_CHANGE
                | TabState.REPAINT_ON_SELECTION_CHANGE
                | TabState.REPAINT_ON_MOUSE_ENTER_TAB
                | TabState.REPAINT_ON_MOUSE_ENTER_CLOSE_BUTTON
                | TabState.REPAINT_ON_MOUSE_PRESSED;
     *</pre>
     *
     *
     * @return  The repaint policy that should be used in conjunction with mouse events to determine when a
     *          repaint is needed.
     */
    protected int createRepaintPolicy () {
        return TabState.REPAINT_SELECTION_ON_ACTIVATION_CHANGE
                | TabState.REPAINT_ON_SELECTION_CHANGE
                | TabState.REPAINT_ON_MOUSE_ENTER_TAB
                | TabState.REPAINT_ON_MOUSE_ENTER_CLOSE_BUTTON
                | TabState.REPAINT_ON_MOUSE_PRESSED;
    }

    /**
     * @return Rectangle of the tab to be repainted
     */ 
    protected Rectangle getTabRectForRepaint( int tab, Rectangle rect ) {
        return getTabRect( tab, rect );
    }

    protected class BasicTabState extends TabState {

        @Override
        public int getState(int tab) {
            if (displayer.getModel().size() == 0) {
                return TabState.NOT_ONSCREEN;
            }
            int result = super.getState(tab);
            if (tab == 0) {
                result |= TabState.LEFTMOST;
            }
            if (tab == displayer.getModel().size() - 1) {
                result |= TabState.RIGHTMOST;
            }
            return result;
        }

        @Override
        protected void repaintAllTabs() {
            //XXX would be nicer to just repaint the tabs area,
            //but we also need to repaint below all the tabs in the
            //event of activated/deactivated.  No actual reason to
            //repaint the buttons here.
            displayer.repaint();
        }

        @Override
        public int getRepaintPolicy(int tab) {
            //Defined in createRepaintPolicy()
            return repaintPolicy;
        }

        @Override
        protected void repaintTab(int tab) {
            if (tab == -1 || tab > displayer.getModel().size()) {
                return;
            }
            getTabRectForRepaint(tab, scratch);
            scratch.y = 0;
            scratch.height = displayer.getHeight();
            displayer.repaint(scratch.x, scratch.y, scratch.width,
                              scratch.height);
        }

        @Override
        boolean isDisplayable() {
            return displayer.isDisplayable();
        }
    }
    
    @Override
    protected ModelListener createModelListener() {
        return new BasicModelListener();
    }    

    private class BasicDisplayerPropertyChangeListener
            extends DisplayerPropertyChangeListener {

        @Override
        protected void activationChanged() {
            tabState.setActive(displayer.isActive());
        }
    }

    protected class BasicDisplayerMouseListener implements MouseListener,
            MouseMotionListener, MouseWheelListener {
        private int updateMouseLocation(MouseEvent e) {
            lastKnownMouseLocation.x = e.getX();
            lastKnownMouseLocation.y = e.getY();
            return tabForCoordinate(lastKnownMouseLocation);
        }

        @Override
        public void mouseClicked(MouseEvent e) {
            int idx = updateMouseLocation(e);
            if (idx == -1) {
                return;
            }

            TabCellRenderer tcr = getTabCellRenderer(idx);
            getTabRect(idx, scratch);
            int state = tabState.getState(idx);

            potentialCommand (idx, e, state, tcr, scratch);
        }

        @Override
        public void mouseDragged(MouseEvent e) {
            mouseMoved (e);
        }

        @Override
        public void mouseEntered(MouseEvent e) {
            int idx = updateMouseLocation(e);
            tabState.setMouseInTabsArea(true);
            tabState.setContainsMouse(idx);
        }

        @Override
        public void mouseExited(MouseEvent e) {
            updateMouseLocation(e);
            tabState.setMouseInTabsArea(false);
            tabState.setContainsMouse(-1);
            tabState.setCloseButtonContainsMouse(-1);
        }

        @Override
        public void mouseMoved(MouseEvent e) {
            int idx = updateMouseLocation(e);
            tabState.setMouseInTabsArea(true);
            tabState.setContainsMouse(idx);
            if (idx != -1) {
                TabCellRenderer tcr = getTabCellRenderer(idx);
                getTabRect(idx, scratch);
                int state = tabState.getState(idx);

                String s = tcr.getCommandAtPoint(e.getPoint(), state, scratch);
                if (TabDisplayer.COMMAND_CLOSE == s) {
                    tabState.setCloseButtonContainsMouse(idx);
                } else {
                    tabState.setCloseButtonContainsMouse(-1);
                }
            } else {
                tabState.setContainsMouse(-1);
            }
        }

        private int lastPressedTab = -1;
        private long pressTime = -1;
        @Override
        public void mousePressed(MouseEvent e) {
            int idx = updateMouseLocation(e);
            tabState.setPressed(idx);

            //One a double click, preserve the tab that was initially clicked, in case
            //a re-layout happened.  We'll pass that to the action.
            long time = e.getWhen();
            if (time - pressTime > 200) {
                lastPressedTab = idx;
            }
            pressTime = time;
            lastPressedTab = idx;
            if (idx != -1) {
                TabCellRenderer tcr = getTabCellRenderer(idx);
                getTabRect(idx, scratch);
                int state = tabState.getState(idx);

                //First find the command for the location with the default button -
                //TabState may trigger a repaint
                String command = tcr.getCommandAtPoint (e.getPoint(), state, scratch);
                if (TabDisplayer.COMMAND_CLOSE == command) {
                    tabState.setCloseButtonContainsMouse(idx);
                    tabState.setMousePressedInCloseButton(idx);

                    //We're closing, don't try to maximize this tab if it turns out to be
                    //a double click
                    pressTime = -1;
                    lastPressedTab = -1;
                }

                potentialCommand (idx, e, state, tcr, scratch);
            } else {
                tabState.setMousePressedInCloseButton(-1); //just in case
                if( e.isPopupTrigger() ) {
                    displayer.repaint();
                    performCommand (TabDisplayer.COMMAND_POPUP_REQUEST, -1, e);
                }
            }
        }

        private void potentialCommand (int idx, MouseEvent e, int state, TabCellRenderer tcr, Rectangle bounds) {
            String command = tcr.getCommandAtPoint (e.getPoint(), state, bounds,
                    e.getButton(), e.getID(), e.getModifiersEx());
            if (command == null || TabDisplayer.COMMAND_SELECT == command) {
                if (e.isPopupTrigger()) {
                    displayer.repaint();
                    performCommand (TabDisplayer.COMMAND_POPUP_REQUEST, idx, e);
                    return;
                } else if (e.getID() == MouseEvent.MOUSE_CLICKED && e.getClickCount() >= 2 && e.getButton() == MouseEvent.BUTTON1 ) {
                    performCommand (TabDisplayer.COMMAND_MAXIMIZE, idx, e);
                    return;
                }
            }

            if (command != null) {
                performCommand (command, lastPressedTab == -1 || lastPressedTab >=
                    displayer.getModel().size() ? idx : lastPressedTab, e);
            }
        }

        private void performCommand (String command, int idx, MouseEvent evt) {
            evt.consume();
            if (TabDisplayer.COMMAND_SELECT == command) {
                if (idx != displayer.getSelectionModel().getSelectedIndex()) {
                    boolean go = shouldPerformAction (command, idx, evt);
                    if (go) {
                        selectionModel.setSelectedIndex (idx);
                    }
                }
            } else {
                boolean should = shouldPerformAction (command, idx, evt) && displayer.isShowCloseButton();
                if (should) {
                    if (TabDisplayer.COMMAND_CLOSE == command) {
                        displayer.getModel().removeTab(idx);
                    } else if (TabDisplayer.COMMAND_CLOSE_ALL == command) {
                        displayer.getModel().removeTabs (0, displayer.getModel().size());
                    } else if (TabDisplayer.COMMAND_CLOSE_ALL_BUT_THIS == command) {
                        int start;
                        int end;
                        if (idx != displayer.getModel().size()-1) {
                            start = idx+1;
                            end = displayer.getModel().size();
                            displayer.getModel().removeTabs(start, end);
                        }
                        if (idx != 0) {
                            start = 0;
                            end = idx;
                            displayer.getModel().removeTabs(start, end);
                        }
                    }
                }
            }
        }

        @Override
        public void mouseReleased(MouseEvent e) {
            int idx = updateMouseLocation(e);
            if (idx != -1) {
                TabCellRenderer tcr = getTabCellRenderer(idx);
                getTabRect(idx, scratch);
                int state = tabState.getState(idx);
                if ((state & TabState.PRESSED) != 0 && ((state & TabState.CLIP_LEFT) != 0) || (state & TabState.CLIP_RIGHT) != 0) {
                    makeTabVisible(idx);
                }
                potentialCommand (idx, e, state, tcr, scratch);
            } else {
                if( e.isPopupTrigger() ) {
                    displayer.repaint();
                    performCommand (TabDisplayer.COMMAND_POPUP_REQUEST, -1, e);
                }
            }
            tabState.setMouseInTabsArea(idx != -1);
            tabState.setPressed(-1);
            tabState.setMousePressedInCloseButton(-1);
        }

        @Override
        public final void mouseWheelMoved(MouseWheelEvent e) {
            updateMouseLocation(e);
            processMouseWheelEvent(e);
        }
    }

    /** A simple selection listener implementation which updates the TabState model
     * with the new selected index from the selection model when it changes.
     */
    protected class BasicSelectionListener implements ChangeListener {
        @Override
        public void stateChanged(ChangeEvent e) {
            assert e.getSource() == selectionModel : "Unknown event source: "
                    + e.getSource();
            int idx = selectionModel.getSelectedIndex();
            tabState.setSelected(idx >= 0 ? idx : -1);
            if (idx >= 0) {
                makeTabVisible (selectionModel.getSelectedIndex());
            }
        }
    }
    
    /**
     * Listener on data model which will pass modified indices to the
     * TabState object, so it can update which tab indices are flashing in
     * "attention" mode, if any.
     */
    protected class BasicModelListener extends AbstractTabDisplayerUI.ModelListener {
        @Override
        public void contentsChanged(ListDataEvent e) {
            super.contentsChanged(e);
            tabState.contentsChanged(e);
        }

        @Override
        public void indicesAdded(ComplexListDataEvent e) {
            super.indicesAdded(e);
            tabState.indicesAdded(e);
        }

        @Override
        public void indicesChanged(ComplexListDataEvent e) {
            tabState.indicesChanged(e);
        }

        @Override
        public void indicesRemoved(ComplexListDataEvent e) {
            tabState.indicesRemoved(e);
        }

        @Override
        public void intervalAdded(ListDataEvent e) {
            tabState.intervalAdded(e);
        }

        @Override
        public void intervalRemoved(ListDataEvent e) {
            tabState.intervalRemoved(e);
        }
    }    
}