2
* Copyright (c) 2005-2010 Laf-Widget Kirill Grouchnikov. All Rights Reserved.
4
* Redistribution and use in source and binary forms, with or without
5
* modification, are permitted provided that the following conditions are met:
7
* o Redistributions of source code must retain the above copyright notice,
8
* this list of conditions and the following disclaimer.
10
* o Redistributions in binary form must reproduce the above copyright notice,
11
* this list of conditions and the following disclaimer in the documentation
12
* and/or other materials provided with the distribution.
14
* o Neither the name of Laf-Widget Kirill Grouchnikov nor the names of
15
* its contributors may be used to endorse or promote products derived
16
* from this software without specific prior written permission.
18
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
20
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
21
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
22
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
23
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
24
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
25
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
26
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
27
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
28
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
package org.pushingpixels.lafwidget.tabbed;
33
import java.awt.event.*;
34
import java.awt.image.BufferedImage;
35
import java.beans.PropertyChangeEvent;
36
import java.beans.PropertyChangeListener;
37
import java.text.MessageFormat;
40
import javax.swing.border.*;
42
import org.pushingpixels.lafwidget.*;
43
import org.pushingpixels.lafwidget.animation.AnimationConfigurationManager;
44
import org.pushingpixels.lafwidget.contrib.blogofbug.swing.components.*;
45
import org.pushingpixels.lafwidget.tabbed.TabPreviewThread.TabPreviewInfo;
46
import org.pushingpixels.lafwidget.utils.ShadowPopupBorder;
47
import org.pushingpixels.lafwidget.utils.LafConstants.TabOverviewKind;
48
import org.pushingpixels.trident.Timeline;
49
import org.pushingpixels.trident.Timeline.TimelineState;
50
import org.pushingpixels.trident.callback.UIThreadTimelineCallbackAdapter;
51
import org.pushingpixels.trident.swing.SwingRepaintCallback;
54
* Tab overview dialog.
56
* @author Kirill Grouchnikov
58
public class TabOverviewDialog extends JDialog {
60
* The associated tabbed pane.
62
protected JTabbedPane tabPane;
65
// * The grid overview panel (with all thumbnails).
67
// protected JPanel gridOverviewPanel;
70
* The associated preview callback.
72
protected TabPreviewThread.TabPreviewCallback previewCallback;
75
* Listener on LAF switches.
77
protected PropertyChangeListener lafSwitchListener;
80
* Handles mouse events on the tab overview dialog (such as highlighting the
81
* currently rolled-over tab preview, closing the overview when a tab
82
* preview is clicked, tooltips etc.)
84
* @author Kirill Grouchnikov
86
protected class TabPreviewMouseHandler extends MouseAdapter {
93
* Tab preview control.
95
private JComponent previewControl;
98
* If <code>true</code>, the preview uses double click to select the tab
99
* and dismiss the tab overview dialog.
101
private boolean useDoubleClick;
104
* If <code>true</code>, the tab preview controls have rollover effects
107
private boolean hasRolloverBorderEffect;
110
* Creates the mouse handler for a single tab preview control.
114
* @param previewControl
115
* Tab preview control.
116
* @param hasRolloverBorderEffect
117
* If <code>true</code>, the preview uses double click to
118
* select the tab and dismiss the tab overview dialog.
119
* @param useDoubleClick
120
* If <code>true</code>, the tab preview controls have
121
* rollover effects on borders.
123
public TabPreviewMouseHandler(int index, JComponent previewControl,
124
boolean hasRolloverBorderEffect, boolean useDoubleClick) {
126
this.previewControl = previewControl;
127
this.useDoubleClick = useDoubleClick;
128
this.hasRolloverBorderEffect = hasRolloverBorderEffect;
132
public void mouseClicked(MouseEvent e) {
133
if (this.useDoubleClick) {
134
if (e.getClickCount() < 2)
138
SwingUtilities.invokeLater(new Runnable() {
141
TabOverviewDialog.this.dispose();
142
TabOverviewDialog.this.tabPane
143
.setSelectedIndex(TabPreviewMouseHandler.this.index);
149
public void mouseEntered(MouseEvent e) {
150
if (!this.hasRolloverBorderEffect)
152
boolean isSelected = (TabOverviewDialog.this.tabPane
153
.getSelectedIndex() == this.index);
154
Border innerBorder = isSelected ? new LineBorder(Color.blue, 2)
155
: new LineBorder(Color.black, 1);
156
this.previewControl.setBorder(new CompoundBorder(
157
new ShadowPopupBorder(), innerBorder));
159
// this.previewControl.setBorder(new LineBorder(Color.blue, 2));
161
// this.previewControl.setBorder(new CompoundBorder(
162
// new EmptyBorder(1, 1, 1, 1), new LineBorder(
163
// Color.black, 1)));
167
public void mouseExited(MouseEvent e) {
168
if (!this.hasRolloverBorderEffect)
170
boolean isSelected = (TabOverviewDialog.this.tabPane
171
.getSelectedIndex() == this.index);
172
Border innerBorder = isSelected ? new LineBorder(Color.black, 2)
173
: new LineBorder(Color.black, 1);
174
this.previewControl.setBorder(new CompoundBorder(
175
new ShadowPopupBorder(), innerBorder));
177
// this.previewControl.setBorder(new LineBorder(Color.black, 2));
179
// this.previewControl.setBorder(new CompoundBorder(
180
// new EmptyBorder(1, 1, 1, 1), new LineBorder(
181
// Color.black, 1)));
186
* Tab round carousel overview panel. Contains a round carousel of tab
187
* preview widgets. The widgets are created in a separate thread (
188
* {@link TabPreviewThread}) and offered to the tab overview dialog via the
189
* registered implementation of {@link TabPreviewThread.TabPreviewCallback}.
190
* This way the application stays interactive while the tab overview dialog
191
* is being populated.
193
* @author Kirill Grouchnikov
195
protected class TabRoundCarouselOverviewPanel extends JPanel {
197
* Tab preview controls.
199
protected ReflectedImageLabel[] previewControls;
202
* Width of a single tab preview control.
204
protected int pWidth;
207
* Height of a single tab preview control.
209
protected int pHeight;
212
* Associated carousel.
214
protected JCarosel carosel;
217
* Creates a tab overview panel.
220
* The width of the parent dialog.
221
* @param dialogHeight
222
* The height of the parent dialog.
224
public TabRoundCarouselOverviewPanel(final int dialogWidth,
225
final int dialogHeight) {
226
// int tabCount = TabOverviewDialog.this.tabPane.getTabCount();
228
// this.previewControls = new HashSet<Component>();
229
TabPreviewThread.TabPreviewCallback previewCallback = new TabPreviewThread.TabPreviewCallback() {
233
* @seeorg.pushingpixels.lafwidget.tabbed.TabPreviewThread.
234
* TabPreviewCallback#start(javax.swing.JTabbedPane, int,
236
* .lafwidget.tabbed.TabPreviewThread.TabPreviewInfo)
239
public void start(JTabbedPane tabPane, int tabCount,
240
TabPreviewInfo tabPreviewInfo) {
241
// Check if need to reallocate the preview controls.
242
boolean isSame = (previewControls != null)
243
&& (previewControls.length == tabCount);
247
if (previewControls != null) {
248
for (int i = 0; i < previewControls.length; i++) {
249
carosel.remove(previewControls[i]);
253
double coef = Math.min(3.5, tabCount / 1.5);
254
coef = Math.max(coef, 4.5);
255
pWidth = (int) (dialogWidth / coef);
256
pHeight = (int) (dialogHeight / coef);
258
tabPreviewInfo.setPreviewWidth(pWidth - 4);
259
tabPreviewInfo.setPreviewHeight(pHeight - 4);
261
previewControls = new ReflectedImageLabel[tabCount];
262
TabPreviewPainter tpp = LafWidgetUtilities2
263
.getTabPreviewPainter(TabOverviewDialog.this.tabPane);
264
for (int i = 0; i < tabCount; i++) {
265
BufferedImage placeHolder = new BufferedImage(pWidth,
266
pHeight, BufferedImage.TYPE_INT_ARGB);
267
Graphics2D g2d = (Graphics2D) placeHolder.getGraphics();
268
g2d.setColor(UIManager.getColor("Label.background"));
269
g2d.fillRect(0, 0, pWidth, pHeight);
270
ReflectedImageLabel ril = (ReflectedImageLabel) carosel
271
.add(placeHolder, tabPane.getTitleAt(i));
272
ril.setForeground(UIManager
273
.getColor("Label.foreground"));
274
ril.setBackground(UIManager
275
.getColor("Label.background"));
276
// TabPreviewControl previewControl = new
277
// TabPreviewControl(
278
// TabOverviewDialog.this.tabPane, i);
279
ril.setPreferredSize(new Dimension(pWidth, pHeight));
280
// fix for issue 177 in Substance (disallowing
282
// of disabled tabs).
283
if (tpp.isSensitiveToEvents(
284
TabOverviewDialog.this.tabPane, i)) {
285
ril.addMouseListener(new TabPreviewMouseHandler(i,
288
.setToolTipText(LafWidgetUtilities
289
.getResourceBundle(tabPane)
291
"TabbedPane.overviewWidgetTooltip"));
293
previewControls[i] = ril;
294
// carosel.add(previewControl);
296
carosel.bringToFront(previewControls[tabPane
297
.getSelectedIndex()]);
298
// System.err.println("Added " + previewControls.length
301
// for (int i = 0; i < tabCount; i++) {
302
// previewControls[i].revalidate();
310
* @seeorg.pushingpixels.lafwidget.tabbed.TabPreviewThread.
311
* TabPreviewCallback#offer(javax.swing.JTabbedPane, int,
312
* java.awt.image.BufferedImage)
315
public void offer(JTabbedPane tabPane, int tabIndex,
316
BufferedImage componentSnap) {
317
int width = componentSnap.getWidth() + 4;
318
int height = componentSnap.getHeight() + 4;
319
BufferedImage result = new BufferedImage(width, height,
320
BufferedImage.TYPE_INT_ARGB);
321
Graphics2D g2d = (Graphics2D) result.getGraphics();
322
g2d.setColor(UIManager.getColor("Label.background"));
323
g2d.fillRect(0, 0, width, height);
324
g2d.setColor(UIManager.getColor("Label.foreground"));
325
g2d.drawRect(0, 0, width - 1, height - 1);
326
g2d.drawImage(componentSnap, 2, 2, null);
328
Icon tabIcon = tabPane.getIconAt(tabIndex);
329
if (tabIcon != null) {
330
tabIcon.paintIcon(tabPane, g2d, 2, 2);
333
// Component caroselComponent = carosel.add(result, tabPane
334
// .getTitleAt(tabIndex));
335
// caroselComponent.setForeground(UIManager
336
// .getColor("Label.foreground"));
338
// System.err.println("Setting image on " + tabIndex);
339
previewControls[tabIndex].setRichImage(result);
340
// System.err.println("Set image on " + tabIndex);
341
previewControls[tabIndex].repaint();
342
// previewControls.add(caroselComponent);
343
// TabRoundCarouselOverviewPanel.this.previewControls[tabIndex]
344
// .setPreviewImage(componentSnap);
348
this.carosel = new JCarosel();
349
this.carosel.setDepthBasedAlpha(true);
350
// // carosel.setBackground(Color.BLACK, Color.DARK_GRAY);
353
// TabOverviewDialog.class
355
// "/contrib/com/blogofbug/examples/images/Acknowledgements.png")
356
// .toString(), "You Rock", 128, 128);
357
// carosel.add(TabOverviewDialog.class.getResource(
358
// "/contrib/com/blogofbug/examples/images/Dock.png")
359
// .toString(), "Docks Rock", 128, 128);
360
// carosel.add(TabOverviewDialog.class.getResource(
361
// "/contrib/com/blogofbug/examples/images/Cascade.png")
362
// .toString(), "Cascade Icon", 128, 128);
363
// carosel.add(TabOverviewDialog.class.getResource(
364
// "/contrib/com/blogofbug/examples/images/Quit.png")
365
// .toString(), "Quit Bugging", 128, 128);
366
this.setLayout(new BorderLayout());
367
this.add(carosel, BorderLayout.CENTER);
369
TabPreviewInfo previewInfo = new TabPreviewInfo();
370
previewInfo.tabPane = TabOverviewDialog.this.tabPane;
371
previewInfo.previewCallback = previewCallback;
372
// previewInfo.previewWidth = this.pWidth - 4;
373
// previewInfo.previewHeight = this.pHeight - 20;
374
previewInfo.toPreviewAllTabs = true;
375
previewInfo.initiator = TabOverviewDialog.this;
377
TabPreviewThread.getInstance().queueTabPreviewRequest(previewInfo);
382
* Tab menu carousel overview panel. Contains a menu carousel of tab preview
383
* widgets. The widgets are created in a separate thread (
384
* {@link TabPreviewThread}) and offered to the tab overview dialog via the
385
* registered implementation of {@link TabPreviewThread.TabPreviewCallback}.
386
* This way the application stays interactive while the tab overview dialog
387
* is being populated.
389
* @author Kirill Grouchnikov
391
protected class TabMenuCarouselOverviewPanel extends JPanel {
393
* Tab preview controls.
395
protected ReflectedImageLabel[] previewControls;
398
* Width of a single tab preview control.
400
protected int pWidth;
403
* Height of a single tab preview control.
405
protected int pHeight;
408
* The associated carousel menu.
410
protected JCarouselMenu caroselMenu;
413
* Cell renderer for the carosel menu. Employs a little trick to provide
414
* LAF-consistent painting of the cells.
416
* @author Kirill Grouchnikov
418
protected class MenuCarouselListCellRenderer extends JLabel implements
421
* The cell renderer from the currently installed LAF.
423
protected ListCellRenderer lafDefaultCellRenderer;
426
* Creates the cell renderer for the carosel menu.
428
* @param lafDefaultCellRenderer
429
* The cell renderer from the currently installed LAF.
431
public MenuCarouselListCellRenderer(
432
ListCellRenderer lafDefaultCellRenderer) {
433
this.lafDefaultCellRenderer = lafDefaultCellRenderer;
434
// if (lafDefaultCellRenderer instanceof Component) {
435
// JComponent jc = (JComponent) lafDefaultCellRenderer;
436
// jc.setBorder(new EmptyBorder(5, 5, 5, 5));
437
// jc.setFont(super.getFont().deriveFont(Font.BOLD, 14.0f));
442
* Sets up the component for stamping
445
public Component getListCellRendererComponent(JList jList,
446
Object object, int i, boolean isSelected,
447
boolean cellHasFocus) {
448
JCarouselMenu.MenuItem item = (JCarouselMenu.MenuItem) object;
449
Component result = this.lafDefaultCellRenderer
450
.getListCellRendererComponent(jList, item.getLabel(),
451
i, isSelected, cellHasFocus);
453
if (result instanceof Component) {
454
JComponent jc = (JComponent) result;
455
jc.setBorder(new EmptyBorder(5, 5, 5, 5));
456
jc.setFont(super.getFont().deriveFont(Font.BOLD, 14.0f));
464
* Creates a tab overview panel.
467
* The width of the parent dialog.
468
* @param dialogHeight
469
* The height of the parent dialog.
471
public TabMenuCarouselOverviewPanel(final int dialogWidth,
472
final int dialogHeight) {
473
// int tabCount = TabOverviewDialog.this.tabPane.getTabCount();
475
// this.previewControls = new HashSet<Component>();
476
TabPreviewThread.TabPreviewCallback previewCallback = new TabPreviewThread.TabPreviewCallback() {
480
* @seeorg.pushingpixels.lafwidget.tabbed.TabPreviewThread.
481
* TabPreviewCallback#start(javax.swing.JTabbedPane, int,
483
* .lafwidget.tabbed.TabPreviewThread.TabPreviewInfo)
486
public void start(JTabbedPane tabPane, int tabCount,
487
TabPreviewInfo tabPreviewInfo) {
488
// Check if need to reallocate the preview controls.
489
boolean isSame = (previewControls != null)
490
&& (previewControls.length == tabCount);
494
if (previewControls != null) {
495
for (int i = 0; i < previewControls.length; i++) {
496
caroselMenu.remove(previewControls[i]);
500
double coef = Math.min(2.8, tabCount / 1.8);
501
coef = Math.max(2.5, coef);
502
pWidth = (int) (dialogWidth / coef);
503
pHeight = (int) (dialogHeight / coef);
505
tabPreviewInfo.setPreviewWidth(pWidth - 4);
506
tabPreviewInfo.setPreviewHeight(pHeight - 4);
508
previewControls = new ReflectedImageLabel[tabCount];
509
TabPreviewPainter tpp = LafWidgetUtilities2
510
.getTabPreviewPainter(TabOverviewDialog.this.tabPane);
511
for (int i = 0; i < tabCount; i++) {
512
BufferedImage placeHolder = new BufferedImage(pWidth,
513
pHeight, BufferedImage.TYPE_INT_ARGB);
514
Graphics2D g2d = (Graphics2D) placeHolder.getGraphics();
515
g2d.setColor(UIManager.getColor("Label.background"));
516
g2d.fillRect(0, 0, pWidth, pHeight);
517
ReflectedImageLabel ril = (ReflectedImageLabel) caroselMenu
518
.add(placeHolder, tabPane.getTitleAt(i));
519
ril.setForeground(UIManager
520
.getColor("Label.foreground"));
521
ril.setBackground(UIManager
522
.getColor("Label.background"));
523
// TabPreviewControl previewControl = new
524
// TabPreviewControl(
525
// TabOverviewDialog.this.tabPane, i);
526
ril.setPreferredSize(new Dimension(pWidth, pHeight));
527
// fix for issue 177 in Substance (disallowing
529
// of disabled tabs).
530
if (tpp.isSensitiveToEvents(
531
TabOverviewDialog.this.tabPane, i)) {
532
ril.addMouseListener(new TabPreviewMouseHandler(i,
535
.setToolTipText(LafWidgetUtilities
536
.getResourceBundle(tabPane)
538
"TabbedPane.overviewWidgetTooltip"));
540
previewControls[i] = ril;
541
// carosel.add(previewControl);
543
caroselMenu.setSelectedIndex(tabPane.getSelectedIndex());
544
// System.err.println("Added " + previewControls.length
547
// for (int i = 0; i < tabCount; i++) {
548
// previewControls[i].revalidate();
556
* @seeorg.pushingpixels.lafwidget.tabbed.TabPreviewThread.
557
* TabPreviewCallback#offer(javax.swing.JTabbedPane, int,
558
* java.awt.image.BufferedImage)
561
public void offer(JTabbedPane tabPane, int tabIndex,
562
BufferedImage componentSnap) {
563
int width = componentSnap.getWidth() + 4;
564
int height = componentSnap.getHeight() + 4;
565
BufferedImage result = new BufferedImage(width, height,
566
BufferedImage.TYPE_INT_ARGB);
567
Graphics2D g2d = (Graphics2D) result.getGraphics();
568
g2d.setColor(UIManager.getColor("Label.background"));
569
g2d.fillRect(0, 0, width, height);
570
g2d.setColor(UIManager.getColor("Label.foreground"));
571
g2d.drawRect(0, 0, width - 1, height - 1);
572
g2d.drawImage(componentSnap, 2, 2, null);
574
Icon tabIcon = tabPane.getIconAt(tabIndex);
575
if (tabIcon != null) {
576
tabIcon.paintIcon(tabPane, g2d, 2, 2);
578
// Component caroselComponent = carosel.add(result, tabPane
579
// .getTitleAt(tabIndex));
580
// caroselComponent.setForeground(UIManager
581
// .getColor("Label.foreground"));
583
// System.err.println("Setting image on " + tabIndex);
584
previewControls[tabIndex].setRichImage(result);
585
// System.err.println("Set image on " + tabIndex);
586
previewControls[tabIndex].repaint();
587
// previewControls.add(caroselComponent);
588
// TabRoundCarouselOverviewPanel.this.previewControls[tabIndex]
589
// .setPreviewImage(componentSnap);
593
this.caroselMenu = new JCarouselMenu(null);
594
JList dummyList = new JList();
595
ListCellRenderer lcr = dummyList.getCellRenderer();
596
this.caroselMenu.setCellRenderer(new MenuCarouselListCellRenderer(
598
this.caroselMenu.setMenuScrollColor(UIManager
599
.getColor("Panel.background"));
600
this.caroselMenu.setUpDownColor(UIManager
601
.getColor("Label.foreground"));
602
LafWidgetSupport support = LafWidgetRepository.getRepository()
604
if (support != null) {
605
this.caroselMenu.setUpDownIcons(support
606
.getArrowIcon(SwingConstants.NORTH), support
607
.getArrowIcon(SwingConstants.SOUTH));
610
// this.carosel.setDepthBasedAlpha(true);
611
// // carosel.setBackground(Color.BLACK, Color.DARK_GRAY);
614
// TabOverviewDialog.class
616
// "/contrib/com/blogofbug/examples/images/Acknowledgements.png")
617
// .toString(), "You Rock", 128, 128);
618
// carosel.add(TabOverviewDialog.class.getResource(
619
// "/contrib/com/blogofbug/examples/images/Dock.png")
620
// .toString(), "Docks Rock", 128, 128);
621
// carosel.add(TabOverviewDialog.class.getResource(
622
// "/contrib/com/blogofbug/examples/images/Cascade.png")
623
// .toString(), "Cascade Icon", 128, 128);
624
// carosel.add(TabOverviewDialog.class.getResource(
625
// "/contrib/com/blogofbug/examples/images/Quit.png")
626
// .toString(), "Quit Bugging", 128, 128);
627
this.setLayout(new BorderLayout());
628
this.add(caroselMenu, BorderLayout.CENTER);
630
TabPreviewInfo previewInfo = new TabPreviewInfo();
631
previewInfo.tabPane = TabOverviewDialog.this.tabPane;
632
previewInfo.previewCallback = previewCallback;
633
previewInfo.setPreviewWidth(this.pWidth - 4);
634
previewInfo.setPreviewHeight(this.pHeight - 4);
635
previewInfo.toPreviewAllTabs = true;
636
previewInfo.initiator = TabOverviewDialog.this;
638
TabPreviewThread.getInstance().queueTabPreviewRequest(previewInfo);
644
* @see javax.swing.JPanel#updateUI()
647
public void updateUI() {
649
if (this.caroselMenu != null) {
650
JList dummyList = new JList();
651
ListCellRenderer lcr = dummyList.getCellRenderer();
653
.setCellRenderer(new MenuCarouselListCellRenderer(lcr));
654
this.caroselMenu.setMenuScrollColor(UIManager
655
.getColor("Panel.background"));
656
this.caroselMenu.setUpDownColor(UIManager
657
.getColor("Label.foreground"));
658
this.caroselMenu.setBackground(UIManager
659
.getColor("Panel.background"));
660
LafWidgetSupport support = LafWidgetRepository.getRepository()
662
if (support != null) {
663
this.caroselMenu.setUpDownIcons(support
664
.getArrowIcon(SwingConstants.NORTH), support
665
.getArrowIcon(SwingConstants.SOUTH));
672
* Tab grid overview panel. Contains a grid of tab preview widgets. The
673
* widgets are created in a separate thread ({@link TabPreviewThread}) and
674
* offered to the tab overview dialog via the registered implementation of
675
* {@link TabPreviewThread.TabPreviewCallback}. This way the application
676
* stays interactive while the tab overview dialog is being populated.
678
* @author Kirill Grouchnikov
680
protected class TabGridOverviewPanel extends JPanel {
682
* Tab preview controls.
684
protected TabPreviewControl[] previewControls;
687
* Width of a single tab preview control.
689
protected int pWidth;
692
* Height of a single tab preview control.
694
protected int pHeight;
697
* Number of overview grid columns.
699
protected int colCount;
702
* Glass pane for rollover effects.
704
protected TabGridOverviewGlassPane glassPane;
707
* Creates a tab overview panel.
710
* The width of the parent dialog.
711
* @param dialogHeight
712
* The height of the parent dialog.
714
public TabGridOverviewPanel(final int dialogWidth,
715
final int dialogHeight) {
716
// int tabCount = TabOverviewDialog.this.tabPane.getTabCount();
718
TabPreviewThread.TabPreviewCallback previewCallback = new TabPreviewThread.TabPreviewCallback() {
722
* @seeorg.pushingpixels.lafwidget.tabbed.TabPreviewThread.
723
* TabPreviewCallback#start(javax.swing.JTabbedPane, int,
725
* .lafwidget.tabbed.TabPreviewThread.TabPreviewInfo)
728
public void start(JTabbedPane tabPane, int tabCount,
729
TabPreviewInfo tabPreviewInfo) {
730
colCount = (int) Math.sqrt(tabCount);
731
if (colCount * colCount < tabCount)
734
pWidth = (dialogWidth - 8) / colCount;
735
pHeight = (dialogHeight - 32) / colCount;
737
tabPreviewInfo.setPreviewWidth(pWidth - 4);
738
tabPreviewInfo.setPreviewHeight(pHeight - 20);
740
// Check if need to reallocate the preview controls.
741
boolean isSame = (previewControls != null)
742
&& (previewControls.length == tabCount);
746
if (previewControls != null) {
747
for (int i = 0; i < previewControls.length; i++) {
748
remove(previewControls[i]);
752
previewControls = new TabPreviewControl[tabCount];
753
TabPreviewPainter tpp = LafWidgetUtilities2
754
.getTabPreviewPainter(TabOverviewDialog.this.tabPane);
755
for (int i = 0; i < tabCount; i++) {
756
TabPreviewControl previewControl = new TabPreviewControl(
757
TabOverviewDialog.this.tabPane, i);
758
// fix for issue 177 in Substance (disallowing selection
759
// of disabled tabs).
760
if (tpp.isSensitiveToEvents(
761
TabOverviewDialog.this.tabPane, i)) {
763
.addMouseListener(new TabPreviewMouseHandler(
764
i, previewControl, true, false));
766
previewControls[i] = previewControl;
771
for (int i = 0; i < tabCount; i++) {
772
previewControls[i].revalidate();
776
JRootPane rp = SwingUtilities
777
.getRootPane(TabGridOverviewPanel.this);
778
glassPane = new TabGridOverviewGlassPane(
779
TabGridOverviewPanel.this);
780
rp.setGlassPane(glassPane);
781
glassPane.setVisible(true);
787
* @seeorg.pushingpixels.lafwidget.tabbed.TabPreviewThread.
788
* TabPreviewCallback#offer(javax.swing.JTabbedPane, int,
789
* java.awt.image.BufferedImage)
792
public void offer(JTabbedPane tabPane, int tabIndex,
793
BufferedImage componentSnap) {
794
TabGridOverviewPanel.this.previewControls[tabIndex]
795
.setPreviewImage(componentSnap, true);
799
this.setLayout(new TabGridOverviewPanelLayout());
801
TabPreviewInfo previewInfo = new TabPreviewInfo();
802
previewInfo.tabPane = TabOverviewDialog.this.tabPane;
803
previewInfo.previewCallback = previewCallback;
804
//previewInfo.setPreviewWidth(this.pWidth - 4);
805
//previewInfo.setPreviewHeight(this.pHeight - 20);
806
previewInfo.toPreviewAllTabs = true;
807
previewInfo.initiator = TabOverviewDialog.this;
809
TabPreviewThread.getInstance().queueTabPreviewRequest(previewInfo);
813
* Layout manager for the tab overview panel.
815
* @author Kirill Grouchnikov
817
private class TabGridOverviewPanelLayout implements LayoutManager {
821
* @see java.awt.LayoutManager#addLayoutComponent(java.lang.String,
822
* java.awt.Component)
825
public void addLayoutComponent(String name, Component comp) {
832
* java.awt.LayoutManager#removeLayoutComponent(java.awt.Component)
835
public void removeLayoutComponent(Component comp) {
841
* @see java.awt.LayoutManager#layoutContainer(java.awt.Container)
844
public void layoutContainer(Container parent) {
845
// int width = parent.getWidth();
846
// int height = parent.getHeight();
848
if (TabGridOverviewPanel.this.previewControls == null)
851
for (int i = 0; i < TabGridOverviewPanel.this.previewControls.length; i++) {
852
TabPreviewControl previewControl = TabGridOverviewPanel.this.previewControls[i];
853
if (previewControl == null)
855
int rowIndex = i / TabGridOverviewPanel.this.colCount;
856
int colIndex = i % TabGridOverviewPanel.this.colCount;
858
previewControl.setBounds(colIndex
859
* TabGridOverviewPanel.this.pWidth, rowIndex
860
* TabGridOverviewPanel.this.pHeight,
861
TabGridOverviewPanel.this.pWidth,
862
TabGridOverviewPanel.this.pHeight);
869
* @see java.awt.LayoutManager#minimumLayoutSize(java.awt.Container)
872
public Dimension minimumLayoutSize(Container parent) {
873
return parent.getSize();
880
* java.awt.LayoutManager#preferredLayoutSize(java.awt.Container)
883
public Dimension preferredLayoutSize(Container parent) {
884
return this.minimumLayoutSize(parent);
890
* Glass pane for the tab grid overview panel. Provides rollover effects,
891
* showing zoomed version of the tab thumbnails.
893
* @author Kirill Grouchnikov
895
public class TabGridOverviewGlassPane extends JPanel {
896
private final class RolloverMouseListener extends MouseAdapter {
897
private final int index;
898
private final TabGridOverviewPanel overviewPanel;
900
private Timeline rolloverTimeline;
902
private RolloverMouseListener(final int index,
903
final TabGridOverviewPanel overviewPanel) {
905
this.overviewPanel = overviewPanel;
906
this.rolloverTimeline = new Timeline(
907
overviewPanel.previewControls[index]);
908
AnimationConfigurationManager.getInstance().configureTimeline(
909
this.rolloverTimeline);
910
this.rolloverTimeline.addPropertyToInterpolate("zoom", 1.0f,
912
this.rolloverTimeline.addCallback(new SwingRepaintCallback(
913
SwingUtilities.getRootPane(overviewPanel)));
914
this.rolloverTimeline
915
.addCallback(new UIThreadTimelineCallbackAdapter() {
917
public void onTimelineStateChanged(
918
TimelineState oldState,
919
TimelineState newState,
920
float durationFraction,
921
float timelinePosition) {
922
if ((oldState == TimelineState.DONE)
923
&& (newState == TimelineState.IDLE)) {
924
overviewPanel.previewControls[index]
925
.setToolTipText(LafWidgetUtilities
926
.getResourceBundle(tabPane)
928
"TabbedPane.overviewWidgetTooltip"));
935
public void mouseEntered(MouseEvent e) {
936
currHoverIndex = index;
937
overviewPanel.previewControls[index].setToolTipText(null);
938
this.rolloverTimeline.play();
942
public void mouseExited(MouseEvent e) {
943
if (currHoverIndex == index)
945
overviewPanel.previewControls[index].setToolTipText(null);
946
this.rolloverTimeline.playReverse();
951
* Index of the tab thumbnail currently under the mouse pointer.
953
private int currHoverIndex;
956
* Mouse listeneres (one for each tab thumbnail).
958
private MouseListener[] mouseListeners;
961
* The associated overview panel.
963
private TabGridOverviewPanel overviewPanel;
966
* Creates the glass pane.
968
* @param overviewPanel
969
* The associated overview panel.
971
public TabGridOverviewGlassPane(final TabGridOverviewPanel overviewPanel) {
972
this.setOpaque(false);
973
this.overviewPanel = overviewPanel;
975
int size = this.overviewPanel.previewControls.length;
976
this.mouseListeners = new MouseListener[size];
977
this.currHoverIndex = -1;
978
for (int i = 0; i < size; i++) {
980
this.mouseListeners[i] = new RolloverMouseListener(index,
982
this.overviewPanel.previewControls[i]
983
.addMouseListener(this.mouseListeners[i]);
990
* @see javax.swing.JComponent#paintComponent(java.awt.Graphics)
993
protected void paintComponent(Graphics g) {
994
Graphics2D graphics = (Graphics2D) g.create();
995
for (int i = 0; i < tabPane.getTabCount(); i++) {
996
TabPreviewControl child = overviewPanel.previewControls[i];
997
if (child.getZoom() > 1.0f) {
998
paintSingleTabComponent(graphics, i);
1001
if (currHoverIndex >= 0) {
1002
// paint the currently hovered once again (so it'll be on top)
1003
paintSingleTabComponent(graphics, currHoverIndex);
1009
* Paints a single tab component.
1014
* Tab component index.
1016
private void paintSingleTabComponent(Graphics2D graphics, int index) {
1017
TabPreviewControl child = overviewPanel.previewControls[index];
1018
Rectangle cBounds = child.getBounds();
1019
int dx = child.getLocationOnScreen().x
1020
- this.getLocationOnScreen().x;
1021
int dy = child.getLocationOnScreen().y
1022
- this.getLocationOnScreen().y;
1023
double factor = child.getZoom();
1024
int bw = (int) (cBounds.width * factor);
1025
int bh = (int) (cBounds.height * factor);
1026
BufferedImage bi = new BufferedImage(bw, bh,
1027
BufferedImage.TYPE_INT_ARGB);
1028
Graphics2D bGraphics = (Graphics2D) bi.getGraphics().create();
1029
bGraphics.scale(factor, factor);
1031
TabPreviewControl tChild = child;
1033
bGraphics.setColor(tChild.getBackground());
1034
bGraphics.fillRect(0, 0, tChild.getWidth(), tChild.getHeight());
1036
Icon icon = tabPane.getIconAt(index);
1037
int iy = (icon == null) ? 16 : icon.getIconHeight();
1039
icon.paintIcon(this, bGraphics, 1, 1);
1041
String title = tabPane.getTitleAt(index);
1042
JLabel tempLabel = new JLabel(title);
1043
tempLabel.setBounds(tChild.titleLabel.getBounds());
1044
tempLabel.setFont(tChild.titleLabel.getFont());
1045
int bdx = tempLabel.getX();
1046
int bdy = tempLabel.getY();
1047
bGraphics.translate(bdx, bdy);
1048
tempLabel.paint(bGraphics);
1049
bGraphics.translate(-bdx, -bdy);
1052
bGraphics.translate(bdx, bdy);
1053
(child).paintTabThumbnail(bGraphics);
1054
bGraphics.translate(-bdx, -bdy);
1055
bGraphics.setColor(Color.black);
1056
bGraphics.drawRect(0, 0, child.getWidth() - 1,
1057
child.getHeight() - 1);
1058
bGraphics.dispose();
1060
dx -= (bw - cBounds.width) / 2;
1061
dy -= (bh - cBounds.height) / 2;
1062
// make sure that the enlarged thumbnail stays inbounds
1063
dx = Math.max(dx, 0);
1064
dy = Math.max(dy, 0);
1065
if (dx + bi.getWidth() > getWidth()) {
1066
dx -= (dx + bi.getWidth() - getWidth());
1068
if (dy + bi.getHeight() > getHeight()) {
1069
dy -= (dy + bi.getHeight() - getHeight());
1071
graphics.drawImage(bi, dx, dy, null);
1076
* Creates a new tab overview dialog. Declared private to enforce usage of
1077
* {@link #getOverviewDialog(JTabbedPane)}.
1081
* @param overviewKind
1084
* Optional owner for the tab overview dialog.
1086
* Modality indication.
1087
* @param dialogWidth
1088
* Tab overview dialog width.
1089
* @param dialogHeight
1090
* Tab overview dialog height.
1091
* @throws HeadlessException
1092
* @see #getOverviewDialog(JTabbedPane)
1094
private TabOverviewDialog(final JTabbedPane tabPane,
1095
TabOverviewKind overviewKind, Frame owner, boolean modal,
1096
int dialogWidth, int dialogHeight) throws HeadlessException {
1097
super(owner, modal);
1098
this.tabPane = tabPane;
1099
this.setLayout(new BorderLayout());
1100
if (overviewKind == TabOverviewKind.GRID) {
1101
TabGridOverviewPanel gridOverviewPanel = new TabGridOverviewPanel(
1102
dialogWidth, dialogHeight);
1103
this.add(gridOverviewPanel, BorderLayout.CENTER);
1105
// TabGridOverviewGlassPane glassPane = new
1106
// TabGridOverviewGlassPane(
1107
// gridOverviewPanel);
1108
// this.setGlassPane(glassPane);
1109
// glassPane.setVisible(true);
1111
if (overviewKind == TabOverviewKind.ROUND_CAROUSEL) {
1112
this.add(new TabRoundCarouselOverviewPanel(dialogWidth,
1113
dialogHeight), BorderLayout.CENTER);
1115
if (overviewKind == TabOverviewKind.MENU_CAROUSEL) {
1117
.add(new TabMenuCarouselOverviewPanel(dialogWidth,
1118
dialogHeight), BorderLayout.CENTER);
1121
this.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
1122
this.setResizable(false);
1124
this.lafSwitchListener = new PropertyChangeListener() {
1126
public void propertyChange(PropertyChangeEvent evt) {
1127
if ("lookAndFeel".equals(evt.getPropertyName())) {
1128
SwingUtilities.invokeLater(new Runnable() {
1132
.updateComponentTreeUI(TabOverviewDialog.this);
1139
UIManager.addPropertyChangeListener(this.lafSwitchListener);
1141
// Cancel all pending preview requests issued by this overview
1142
// dialog when it closes.
1143
this.addWindowListener(new WindowAdapter() {
1148
* java.awt.event.WindowAdapter#windowClosing(java.awt.event.WindowEvent
1152
public void windowClosing(WindowEvent e) {
1153
this.cancelRequests();
1154
UIManager.removePropertyChangeListener(lafSwitchListener);
1161
* java.awt.event.WindowAdapter#windowClosed(java.awt.event.WindowEvent
1165
public void windowClosed(WindowEvent e) {
1166
this.cancelRequests();
1167
UIManager.removePropertyChangeListener(lafSwitchListener);
1171
* Cancels preview requests issued by <code>this</code> overview
1174
private void cancelRequests() {
1175
if (TabPreviewThread.instanceRunning()) {
1176
TabPreviewThread.getInstance().cancelTabPreviewRequests(
1177
TabOverviewDialog.this);
1184
* Returns a new instance of a tab overview dialog.
1188
* @return Tab overview dialog for the specified tabbed pane.
1190
public static TabOverviewDialog getOverviewDialog(JTabbedPane tabPane) {
1191
final TabPreviewPainter previewPainter = LafWidgetUtilities2
1192
.getTabPreviewPainter(tabPane);
1193
String title = previewPainter.toUpdatePeriodically(tabPane) ? MessageFormat
1194
.format(LafWidgetUtilities.getResourceBundle(tabPane)
1195
.getString("TabbedPane.overviewDialogTitleRefresh"),
1196
new Object[] {previewPainter
1197
.getUpdateCycle(tabPane) / 1000})
1198
: LafWidgetUtilities.getResourceBundle(tabPane).getString(
1199
"TabbedPane.overviewDialogTitle");
1200
JFrame frameForModality = previewPainter.getModalOwner(tabPane);
1201
boolean isModal = (frameForModality != null);
1202
Rectangle dialogScreenBounds = previewPainter
1203
.getPreviewDialogScreenBounds(tabPane);
1204
TabOverviewKind overviewKind = previewPainter.getOverviewKind(tabPane);
1205
final TabOverviewDialog overviewDialog = new TabOverviewDialog(tabPane,
1206
overviewKind, frameForModality, isModal,
1207
dialogScreenBounds.width, dialogScreenBounds.height);
1208
overviewDialog.setTitle(title);
1210
overviewDialog.setLocation(dialogScreenBounds.x, dialogScreenBounds.y);
1211
overviewDialog.setSize(dialogScreenBounds.width,
1212
dialogScreenBounds.height);
1214
// make sure that the tab overview dialog is disposed when
1216
final PropertyChangeListener activeWindowListener = new PropertyChangeListener() {
1218
public void propertyChange(PropertyChangeEvent evt) {
1219
if ("activeWindow".equals(evt.getPropertyName())) {
1220
if (overviewDialog == evt.getOldValue()) {
1221
if (previewPainter.toDisposeOverviewOnFocusLoss()) {
1222
overviewDialog.dispose();
1228
KeyboardFocusManager.getCurrentKeyboardFocusManager()
1229
.addPropertyChangeListener(activeWindowListener);
1231
// make sure that when the window with the tabbed pane is
1232
// closed, the tab overview dialog is disposed.
1233
final Window tabWindow = SwingUtilities.getWindowAncestor(tabPane);
1234
final WindowListener tabWindowListener = new WindowAdapter() {
1236
public void windowClosed(WindowEvent e) {
1237
overviewDialog.dispose();
1240
tabWindow.addWindowListener(tabWindowListener);
1241
overviewDialog.addWindowListener(new WindowAdapter() {
1243
public void windowClosed(WindowEvent e) {
1244
tabWindow.removeWindowListener(tabWindowListener);
1245
KeyboardFocusManager.getCurrentKeyboardFocusManager()
1246
.removePropertyChangeListener(activeWindowListener);
1250
return overviewDialog;