2
* Copyright (c) 2005-2010 Flamingo / Substance 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 Flamingo 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.substance.flamingo.ribbon.ui;
33
import java.awt.geom.Arc2D;
34
import java.awt.geom.GeneralPath;
35
import java.util.HashMap;
39
import javax.swing.border.EmptyBorder;
40
import javax.swing.event.ChangeEvent;
41
import javax.swing.event.ChangeListener;
43
import org.pushingpixels.flamingo.api.ribbon.*;
44
import org.pushingpixels.flamingo.internal.ui.ribbon.BasicRibbonUI;
45
import org.pushingpixels.flamingo.internal.ui.ribbon.RibbonUI;
46
import org.pushingpixels.flamingo.internal.ui.ribbon.appmenu.JRibbonApplicationMenuButton;
47
import org.pushingpixels.flamingo.internal.utils.FlamingoUtilities;
48
import org.pushingpixels.lafwidget.LafWidgetUtilities;
49
import org.pushingpixels.lafwidget.utils.RenderingUtils;
50
import org.pushingpixels.substance.api.*;
51
import org.pushingpixels.substance.api.painter.border.SubstanceBorderPainter;
52
import org.pushingpixels.substance.api.painter.fill.MatteFillPainter;
53
import org.pushingpixels.substance.internal.painter.DecorationPainterUtils;
54
import org.pushingpixels.substance.internal.painter.SeparatorPainterUtils;
55
import org.pushingpixels.substance.internal.ui.SubstanceRootPaneUI;
56
import org.pushingpixels.substance.internal.utils.*;
59
* Custom title pane for {@link JRibbonFrame} running under Substance
62
* @author Kirill Grouchnikov
64
public class SubstanceRibbonFrameTitlePane extends SubstanceTitlePane {
66
* Custom component to paint the header of a single contextual task group.
68
* @author Kirill Grouchnikov
70
private class SubstanceContextualGroupComponent extends JComponent {
72
* The associated contextual task group.
74
RibbonContextualTaskGroup taskGroup;
77
* Creates the new task group header component.
80
* The associated contextual task group.
82
public SubstanceContextualGroupComponent(
83
RibbonContextualTaskGroup taskGroup) {
84
this.taskGroup = taskGroup;
85
this.setOpaque(false);
91
* @see javax.swing.JComponent#paintComponent(java.awt.Graphics)
94
protected void paintComponent(Graphics g) {
95
int width = this.getWidth();
96
int height = this.getHeight();
97
Color hueColor = this.taskGroup.getHueColor();
99
Graphics2D g2d = (Graphics2D) g.create();
100
Paint paint = new GradientPaint(0, 0, SubstanceColorUtilities
101
.getAlphaColor(hueColor, 0), 0, height,
102
SubstanceColorUtilities.getAlphaColor(hueColor,
103
(int) (255 * RibbonContextualTaskGroup.HUE_ALPHA)));
104
g2d = (Graphics2D) g.create();
105
// translucent gradient paint
107
g2d.fillRect(0, 0, width, height);
108
// and a solid line at the bottom
109
g2d.setColor(hueColor);
110
g2d.drawLine(1, height - 1, width, height - 1);
112
JRibbon ribbon = getRibbon();
114
SubstanceColorScheme scheme = SubstanceCoreUtilities.getSkin(
115
rootPane).getEnabledColorScheme(
116
DecorationAreaType.PRIMARY_TITLE_PANE);
119
FontMetrics fm = this.getFontMetrics(ribbon.getFont());
120
int yOffset = (height - fm.getHeight()) / 2;
121
RenderingUtils.installDesktopHints(g2d, this);
123
int offset = SubstanceSizeUtils.getAdjustedSize(SubstanceSizeUtils
124
.getComponentFontSize(this), 5, 2, 1, false);
125
if (getComponentOrientation().isLeftToRight()) {
126
SubstanceTextUtilities.paintText(g2d, this, new Rectangle(
127
offset, yOffset, width, height - yOffset),
128
this.taskGroup.getTitle(), -1, ribbon.getFont(),
129
SubstanceColorUtilities.getForegroundColor(scheme),
132
SubstanceTextUtilities.paintText(g2d, this, new Rectangle(width
134
- g2d.getFontMetrics().stringWidth(
135
this.taskGroup.getTitle()), yOffset, width,
136
height - yOffset), this.taskGroup.getTitle(), -1,
137
ribbon.getFont(), SubstanceColorUtilities
138
.getForegroundColor(scheme), null);
142
SeparatorPainterUtils.paintSeparator(ribbon, g2d, 2, height,
143
SwingConstants.VERTICAL, false, height / 3, 0, true);
146
g2d.translate(width - 1, 0);
147
SeparatorPainterUtils.paintSeparator(ribbon, g2d, 2, height,
148
SwingConstants.VERTICAL, false, height / 3, 0, true);
155
* The taskbar panel that holds the {@link JRibbon#getTaskbarComponents()}.
157
* @author Kirill Grouchnikov
159
private class TaskbarPanel extends JPanel {
161
* Creates the new taskbar panel.
163
public TaskbarPanel() {
164
super(new TaskbarLayout());
165
this.setOpaque(false);
166
int insets = SubstanceSizeUtils.getAdjustedSize(SubstanceSizeUtils
167
.getComponentFontSize(this), 2, 3, 1, false);
168
this.setBorder(new EmptyBorder(insets, insets, insets, insets));
174
* @see javax.swing.JComponent#paintComponent(java.awt.Graphics)
177
protected void paintComponent(Graphics g) {
181
* Returns the outline of this taskbar panel.
185
* @return The outline of this taskbar panel.
187
protected Shape getOutline(double insets) {
188
double width = this.getWidth() - 2 * insets - 1;
189
double height = this.getHeight() - 2 * insets - 1;
190
float radius = (float) height;
192
if (getComponentCount() == 0) {
195
GeneralPath outline = new GeneralPath();
196
JRibbonApplicationMenuButton menuButton = FlamingoUtilities
197
.getApplicationMenuButton(SwingUtilities
198
.getWindowAncestor(this));
200
boolean ltr = getComponentOrientation().isLeftToRight();
202
double alpha = Math.asin((radius - insets / 2)
203
/ (radius + insets / 2));
207
outline.moveTo(insets + width - height / 2, insets);
211
.append(new Arc2D.Double(insets + width - height,
212
insets, height, height, 90, -180,
215
outline.lineTo(insets, insets + height);
216
if (menuButton != null) {
219
arcSpan = 180.0 * alpha / Math.PI;
221
outline.append(new Arc2D.Double(insets - 2 * height,
222
insets, 2 * height, 2 * height, 0, arcSpan,
225
outline.lineTo(insets, insets);
229
outline.moveTo(insets + height / 2, 0);
231
outline.append(new Arc2D.Double(insets, 0, height, height,
232
90, 180, Arc2D.OPEN), true);
233
// bottom right corner
234
outline.lineTo(width - 1, insets + height);
235
if (menuButton != null) {
236
double arcSpan = -90;
238
arcSpan = -180.0 * alpha / Math.PI;
240
outline.append(new Arc2D.Double(width - 1, 0,
241
2 * height, 2 * height, 180, arcSpan,
244
outline.lineTo(width - 1, 0);
256
* @see javax.swing.JComponent#getPreferredSize()
259
public Dimension getPreferredSize() {
260
Dimension result = super.getPreferredSize();
261
return new Dimension(result.width + result.height / 2,
267
* Maps the currently visible contextual task groups to the respective child
268
* components of this title pane.
270
protected Map<RibbonContextualTaskGroup, SubstanceContextualGroupComponent> taskComponentMap;
273
* Listener to sync {@link #taskComponentMap}.
275
protected ChangeListener ribbonFrameChangeListener;
278
* Panel for the taskbar components.
280
protected TaskbarPanel taskbarPanel;
283
* Creates a new title pane for {@link JRibbonFrame}.
290
public SubstanceRibbonFrameTitlePane(JRootPane root, SubstanceRootPaneUI ui) {
292
this.taskComponentMap = new HashMap<RibbonContextualTaskGroup, SubstanceContextualGroupComponent>();
293
this.taskbarPanel = new TaskbarPanel();
294
this.markExtraComponent(this.taskbarPanel, ExtraComponentKind.LEADING);
295
this.add(this.taskbarPanel);
301
* @see org.jvnet.substance.utils.SubstanceTitlePane#createLayout()
304
protected LayoutManager createLayout() {
305
return new RibbonFrameTitlePaneLayout();
311
* @see org.jvnet.substance.utils.SubstanceTitlePane#addNotify()
314
public void addNotify() {
317
JRibbon ribbon = this.getRibbon();
318
ribbon.putClientProperty(BasicRibbonUI.IS_USING_TITLE_PANE,
321
this.syncRibbonState();
323
this.ribbonFrameChangeListener = new ChangeListener() {
325
public void stateChanged(ChangeEvent e) {
329
ribbon.addChangeListener(this.ribbonFrameChangeListener);
335
* @see org.jvnet.substance.utils.SubstanceTitlePane#removeNotify()
338
public void removeNotify() {
339
JRibbon ribbon = this.getRibbon();
340
ribbon.putClientProperty(BasicRibbonUI.IS_USING_TITLE_PANE, null);
342
for (SubstanceContextualGroupComponent groupComp : this.taskComponentMap
344
this.remove(groupComp);
347
ribbon.removeChangeListener(this.ribbonFrameChangeListener);
348
this.ribbonFrameChangeListener = null;
350
super.removeNotify();
354
* Synchronizes the child components for ribbon state (visible contextual
355
* task groups and taskbar components).
357
protected void syncRibbonState() {
358
// Contextual task groups
359
for (SubstanceContextualGroupComponent groupComp : this.taskComponentMap
361
this.remove(groupComp);
363
this.taskComponentMap.clear();
364
JRibbon ribbon = this.getRibbon();
365
for (int i = 0; i < ribbon.getContextualTaskGroupCount(); i++) {
366
RibbonContextualTaskGroup group = ribbon.getContextualTaskGroup(i);
367
if (!ribbon.isVisible(group))
369
SubstanceContextualGroupComponent taskGroupComponent = new SubstanceContextualGroupComponent(
371
taskGroupComponent.applyComponentOrientation(this.getRibbon()
372
.getComponentOrientation());
373
this.add(taskGroupComponent);
374
this.taskComponentMap.put(group, taskGroupComponent);
375
this.markExtraComponent(taskGroupComponent,
376
ExtraComponentKind.MIDDLING);
378
// Taskbar components
379
this.taskbarPanel.removeAll();
380
this.taskbarPanel.setPreferredSize(null);
381
for (Component taskbarComp : ribbon.getTaskbarComponents()) {
382
this.taskbarPanel.add(taskbarComp);
387
* Custom layout manager for the title panes of {@link JRibbonFrame} under
390
* @author Kirill Grouchnikov
392
protected class RibbonFrameTitlePaneLayout extends TitlePaneLayout {
396
* @seeorg.jvnet.substance.utils.SubstanceTitlePane.TitlePaneLayout#
397
* layoutContainer(java.awt.Container)
400
public void layoutContainer(Container c) {
401
super.layoutContainer(c);
403
JRibbon ribbon = getRibbon();
404
boolean ltr = ribbon.getComponentOrientation().isLeftToRight();
405
RibbonUI ribbonUI = ribbon.getUI();
408
// headers of contextual task groups
409
for (Map.Entry<RibbonContextualTaskGroup, SubstanceContextualGroupComponent> entry : taskComponentMap
411
Rectangle taskGroupBounds = ribbonUI
412
.getContextualTaskGroupBounds(entry.getKey());
413
// make sure that the header bounds do not overlap with the
414
// min / max / close buttons
415
int minTrailingX = c.getWidth();
417
for (int i = 0; i < c.getComponentCount(); i++) {
418
Component child = c.getComponent(i);
419
if (!child.isVisible())
421
if (child instanceof JComponent) {
422
ExtraComponentKind kind = (ExtraComponentKind) ((JComponent) child)
423
.getClientProperty(EXTRA_COMPONENT_KIND);
424
if (kind == ExtraComponentKind.LEADING)
426
if (child instanceof SubstanceContextualGroupComponent)
429
minTrailingX = Math.min(child.getX(), minTrailingX);
433
int width = taskGroupBounds.width;
434
if (taskGroupBounds.x + width > (minTrailingX - 5)) {
435
width = minTrailingX - 5 - taskGroupBounds.x;
437
entry.getValue().setBounds(
438
new Rectangle(taskGroupBounds.x, 0, width, c
440
// hide headers when the task toggle buttons
441
// are wrapped with visible scroller buttons
442
entry.getValue().setVisible(
443
!ribbonUI.isShowingScrollsForTaskToggleButtons());
447
taskbarPanel.setVisible(true);
448
Dimension pref = taskbarPanel.getPreferredSize();
449
if (taskbarPanel.getComponentCount() == 0) {
450
// fix for issue 38 on Flamingo - if there are no
451
// taskbar components, don't push the title to the right
455
SubstanceRibbonRootPaneUI rootPaneUI = (SubstanceRibbonRootPaneUI) getRootPane()
457
JRibbonApplicationMenuButton menuButton = rootPaneUI.applicationMenuButton;
459
if (menuButton != null) {
460
if (menuButton.isVisible()) {
461
int maxLeadingX = menuButton.getX()
462
+ menuButton.getWidth() + 2
463
* getTaskBarLayoutGap(taskbarPanel);
464
if (taskbarPanel.isVisible()) {
465
taskbarPanel.setBounds(maxLeadingX, 0, pref.width,
468
menuBar.setVisible(false);
470
if (taskbarPanel.isVisible()) {
471
if (pref.width == 0) {
472
taskbarPanel.setBounds(menuBar.getX()
473
+ menuBar.getWidth(), 0, pref.width, c
476
taskbarPanel.setBounds(menuBar.getX()
477
+ menuBar.getWidth() + 5, 0,
478
pref.width, c.getHeight());
481
menuBar.setVisible(true);
484
menuBar.setVisible(true);
487
// headers of contextual task groups
488
for (Map.Entry<RibbonContextualTaskGroup, SubstanceContextualGroupComponent> entry : taskComponentMap
490
Rectangle taskGroupBounds = ribbonUI
491
.getContextualTaskGroupBounds(entry.getKey());
492
// make sure that the header bounds do not overlap with the
493
// min / max / close buttons
494
int maxTrailingX = 0;
496
for (int i = 0; i < c.getComponentCount(); i++) {
497
Component child = c.getComponent(i);
498
if (!child.isVisible())
500
if (child instanceof JComponent) {
501
ExtraComponentKind kind = (ExtraComponentKind) ((JComponent) child)
502
.getClientProperty(EXTRA_COMPONENT_KIND);
503
if (kind == ExtraComponentKind.LEADING)
505
if (child instanceof SubstanceContextualGroupComponent)
508
maxTrailingX = Math.max(child.getX()
509
+ child.getWidth(), maxTrailingX);
513
int width = taskGroupBounds.width;
514
int x = taskGroupBounds.x;
515
if (taskGroupBounds.x < (maxTrailingX + 5)) {
516
int diff = maxTrailingX + 5 - taskGroupBounds.x;
520
entry.getValue().setBounds(
521
new Rectangle(x, 0, width, c.getHeight()));
522
// hide headers when the task toggle buttons
523
// are wrapped with visible scroller buttons
524
entry.getValue().setVisible(
525
!ribbonUI.isShowingScrollsForTaskToggleButtons());
529
taskbarPanel.setVisible(true);
530
Dimension pref = taskbarPanel.getPreferredSize();
531
if (taskbarPanel.getComponentCount() == 0) {
532
// fix for issue 38 on Flamingo - if there are no
533
// taskbar components, don't push the title to the left
537
SubstanceRibbonRootPaneUI rootPaneUI = (SubstanceRibbonRootPaneUI) getRootPane()
539
JRibbonApplicationMenuButton menuButton = rootPaneUI.applicationMenuButton;
541
if (menuButton != null) {
542
if (menuButton.isVisible()) {
543
int maxLeadingX = menuButton.getX() - 2
544
* getTaskBarLayoutGap(taskbarPanel);
545
if (taskbarPanel.isVisible()) {
546
taskbarPanel.setBounds(maxLeadingX - pref.width, 0,
547
pref.width, c.getHeight());
549
menuBar.setVisible(false);
551
if (taskbarPanel.isVisible()) {
552
if (pref.width == 0) {
553
taskbarPanel.setBounds(menuBar.getX(), 0,
554
pref.width, c.getHeight());
556
taskbarPanel.setBounds(menuBar.getX() - 5
557
- pref.width, 0, pref.width, c
561
menuBar.setVisible(true);
564
menuBar.setVisible(true);
571
* Layout for the task bar.
573
* @author Kirill Grouchnikov
575
private class TaskbarLayout implements LayoutManager {
579
* @see java.awt.LayoutManager#addLayoutComponent(java.lang.String,
580
* java.awt.Component)
583
public void addLayoutComponent(String name, Component c) {
589
* @see java.awt.LayoutManager#removeLayoutComponent(java.awt.Component)
592
public void removeLayoutComponent(Component c) {
598
* @see java.awt.LayoutManager#preferredLayoutSize(java.awt.Container)
601
public Dimension preferredLayoutSize(Container c) {
602
Insets ins = c.getInsets();
604
int gap = getTaskBarLayoutGap(c);
605
for (Component regComp : getRibbon().getTaskbarComponents()) {
606
// Do not add layout space for non-visible components
607
if (regComp.isVisible()){
608
pw += regComp.getPreferredSize().width;
612
return new Dimension(pw + ins.left + ins.right, c.getParent()
619
* @see java.awt.LayoutManager#minimumLayoutSize(java.awt.Container)
622
public Dimension minimumLayoutSize(Container c) {
623
return this.preferredLayoutSize(c);
629
* @see java.awt.LayoutManager#layoutContainer(java.awt.Container)
632
public void layoutContainer(Container c) {
633
Insets ins = c.getInsets();
634
int gap = getTaskBarLayoutGap(c);
635
boolean ltr = getComponentOrientation().isLeftToRight();
636
int x = ltr ? ins.left : c.getWidth() - ins.right;
637
for (Component regComp : getRibbon().getTaskbarComponents()) {
638
// Do not add layout space for non-visible components
639
if (regComp.isVisible()){
640
int pw = regComp.getPreferredSize().width;
642
regComp.setBounds(x, ins.top, pw, c.getHeight() - ins.top
646
regComp.setBounds(x - pw, ins.top, pw, c.getHeight()
647
- ins.top - ins.bottom);
656
* Retrieves the {@link JRibbon} component of the associated
657
* {@link JRibbonFrame}.
659
* @return {@link JRibbon} component of the associated {@link JRibbonFrame}.
661
private JRibbon getRibbon() {
662
JRibbonFrame ribbonFrame = (JRibbonFrame) SwingUtilities
663
.getWindowAncestor(this);
664
JRibbon ribbon = ribbonFrame.getRibbon();
669
* Returns the layout gap of the taskbar panel.
673
* @return Layout gap of the taskbar panel.
675
private int getTaskBarLayoutGap(Container c) {
676
return SubstanceSizeUtils.getAdjustedSize(SubstanceSizeUtils
677
.getComponentFontSize(c), 1, 6, 1, false);
684
* org.jvnet.substance.utils.SubstanceTitlePane#paintComponent(java.awt.
688
public void paintComponent(Graphics g) {
689
super.paintComponent(g);
691
Graphics2D g2d = (Graphics2D) g.create();
692
if (taskbarPanel.getWidth() != 0) {
693
g2d.translate(taskbarPanel.getX(), taskbarPanel.getY());
694
// paint the outline of the taskbar panel to complete
695
// the correct appearance in the area behind the application
697
// g2d.clipRect(0, 0, 20, 100);
698
paintTaskBarPanelOutline(g2d, this.taskbarPanel);
699
g2d.translate(-taskbarPanel.getX(), -taskbarPanel.getY());
702
if (SubstanceLookAndFeel.getCurrentSkin(this).getOverlayPainters(
703
DecorationAreaType.PRIMARY_TITLE_PANE).isEmpty()) {
704
// g2d.translate(0, this.getHeight() - 1);
705
SubstanceColorScheme compScheme = SubstanceColorSchemeUtilities
706
.getColorScheme(this, ColorSchemeAssociationKind.SEPARATOR,
707
ComponentState.ENABLED);
708
Color sepColor = compScheme.isDark() ? SeparatorPainterUtils
709
.getSeparatorShadowColor(compScheme)
710
: SeparatorPainterUtils.getSeparatorDarkColor(compScheme);
711
g2d.setColor(sepColor);
712
g2d.drawLine(0, this.getHeight() - 1, this.getWidth(), this
714
// SeparatorPainterUtils.paintSeparator(this, g2d, this.getWidth(),
716
// JSeparator.HORIZONTAL, false, 0);
722
* Paints the outline of the taskbar panel.
726
* @param taskbarPanel
729
protected static void paintTaskBarPanelOutline(Graphics g,
730
TaskbarPanel taskbarPanel) {
731
int borderDelta = (int) Math.floor(SubstanceSizeUtils
732
.getBorderStrokeWidth(SubstanceSizeUtils
733
.getComponentFontSize(taskbarPanel)) / 2.0);
734
int borderThickness = (int) SubstanceSizeUtils
735
.getBorderStrokeWidth(SubstanceSizeUtils
736
.getComponentFontSize(taskbarPanel));
738
Shape contour = taskbarPanel.getOutline(borderDelta);
739
Shape contourInner = taskbarPanel.getOutline(borderDelta
742
SubstanceColorScheme colorScheme = SubstanceColorSchemeUtilities
743
.getColorScheme(taskbarPanel, ComponentState.ENABLED);
744
SubstanceColorScheme borderScheme = SubstanceColorSchemeUtilities
745
.getColorScheme(taskbarPanel,
746
ColorSchemeAssociationKind.BORDER,
747
ComponentState.ENABLED);
748
SubstanceBorderPainter borderPainter = SubstanceCoreUtilities
749
.getBorderPainter(taskbarPanel);
751
Graphics2D g2d = (Graphics2D) g.create();
752
g2d.setComposite(AlphaComposite.SrcOver.derive(0.6f));
753
if (contour != null) {
754
Shape clip = g2d.getClip();
756
DecorationPainterUtils.paintDecorationBackground(g2d, taskbarPanel,
758
g2d.setComposite(LafWidgetUtilities.getAlphaComposite(taskbarPanel,
760
MatteFillPainter.INSTANCE.paintContourBackground(g2d, taskbarPanel,
761
taskbarPanel.getWidth(), taskbarPanel.getHeight(), contour
762
.getBounds(), false, colorScheme, false);
763
g2d.setComposite(LafWidgetUtilities.getAlphaComposite(taskbarPanel,
768
borderPainter.paintBorder(g2d, taskbarPanel, taskbarPanel.getWidth(),
769
taskbarPanel.getHeight(), contour, contourInner, borderScheme);