Browse Source

Added TabFrame component.

pull/15/head
weisj 5 years ago
parent
commit
97b3d1d87b
  1. 1
      build.gradle
  2. 1
      darklaf.iml
  3. 104
      src/main/java/com/weis/darklaf/components/Insets2D.java
  4. 54
      src/main/java/com/weis/darklaf/components/JLabelUIResource.java
  5. 47
      src/main/java/com/weis/darklaf/components/JPanelUIResource.java
  6. 56
      src/main/java/com/weis/darklaf/components/UIResourceWrapper.java
  7. 14
      src/main/java/com/weis/darklaf/components/border/MutableLineBorder.java
  8. 213
      src/main/java/com/weis/darklaf/components/tabframe/PanelPopup.java
  9. 67
      src/main/java/com/weis/darklaf/components/tabframe/PopupContainer.java
  10. 45
      src/main/java/com/weis/darklaf/components/tabframe/TabArea.java
  11. 862
      src/main/java/com/weis/darklaf/components/tabframe/TabFrame.java
  12. 83
      src/main/java/com/weis/darklaf/components/tabframe/TabFrameContent.java
  13. 449
      src/main/java/com/weis/darklaf/components/tabframe/TabFrameContentPane.java
  14. 158
      src/main/java/com/weis/darklaf/components/tabframe/TabFramePopup.java
  15. 108
      src/main/java/com/weis/darklaf/components/tabframe/TabFrameTab.java
  16. 153
      src/main/java/com/weis/darklaf/components/tabframe/TabFrameTabContainer.java
  17. 167
      src/main/java/com/weis/darklaf/components/tabframe/TabFrameTabLabel.java
  18. 31
      src/main/java/com/weis/darklaf/components/tabframe/TabFrameUI.java
  19. 154
      src/main/java/com/weis/darklaf/components/tabframe/TabbedPopup.java
  20. 159
      src/main/java/com/weis/darklaf/components/tabframe/ToggleSplitPane.java
  21. 91
      src/main/java/com/weis/darklaf/decorators/HoverListener.java
  22. 116
      src/main/java/com/weis/darklaf/icons/RotatableIcon.java
  23. 2
      src/main/java/com/weis/darklaf/theme/Theme.java
  24. 3
      src/main/java/com/weis/darklaf/ui/button/DarkButtonUI.java
  25. 223
      src/main/java/com/weis/darklaf/ui/menu/DarkMenuItemUIBase.java
  26. 249
      src/main/java/com/weis/darklaf/ui/menu/DarkMenuUI.java
  27. 33
      src/main/java/com/weis/darklaf/ui/splitpane/DarkSplitPaneBorder.java
  28. 30
      src/main/java/com/weis/darklaf/ui/splitpane/DarkSplitPaneUI.java
  29. 12
      src/main/java/com/weis/darklaf/ui/tabbedpane/DarkHandler.java
  30. 8
      src/main/java/com/weis/darklaf/ui/tabbedpane/DarkScrollableTabSupport.java
  31. 2
      src/main/java/com/weis/darklaf/ui/tabbedpane/DarkTabAreaButton.java
  32. 7
      src/main/java/com/weis/darklaf/ui/tabbedpane/DarkTabbedPaneScrollLayout.java
  33. 85
      src/main/java/com/weis/darklaf/ui/tabbedpane/DarkTabbedPaneUI.java
  34. 4
      src/main/java/com/weis/darklaf/ui/tabbedpane/MoreTabsButton.java
  35. 23
      src/main/java/com/weis/darklaf/ui/tabbedpane/NewTabButton.java
  36. 4
      src/main/java/com/weis/darklaf/ui/tabbedpane/ScrollableTabSupport.java
  37. 2
      src/main/java/com/weis/darklaf/ui/tabbedpane/TabbedPaneHandler.java
  38. 382
      src/main/java/com/weis/darklaf/ui/tabframe/DarkPanelPopupUI.java
  39. 33
      src/main/java/com/weis/darklaf/ui/tabframe/DarkTabFramePopupHeaderBorder.java
  40. 33
      src/main/java/com/weis/darklaf/ui/tabframe/DarkTabFrameTabBorder.java
  41. 190
      src/main/java/com/weis/darklaf/ui/tabframe/DarkTabFrameTabContainerUI.java
  42. 274
      src/main/java/com/weis/darklaf/ui/tabframe/DarkTabFrameTabLabelUI.java
  43. 188
      src/main/java/com/weis/darklaf/ui/tabframe/DarkTabFrameUI.java
  44. 190
      src/main/java/com/weis/darklaf/ui/tabframe/DarkTabbedPopupUI.java
  45. 229
      src/main/java/com/weis/darklaf/ui/tabframe/TabFrameLayout.java
  46. 163
      src/main/java/com/weis/darklaf/ui/table/DarkTableHeaderUIBridge.java
  47. 100
      src/main/java/com/weis/darklaf/ui/table/DarkTableUI.java
  48. 77
      src/main/java/com/weis/darklaf/ui/text/DarkCaret.java
  49. 16
      src/main/java/com/weis/darklaf/ui/text/DarkTextFieldUI.java
  50. 10
      src/main/java/com/weis/darklaf/ui/text/DarkTextPaneUI.java
  51. 4
      src/main/java/com/weis/darklaf/util/DarkUIUtil.java
  52. 489
      src/main/java/org/pbjar/jxlayer/plaf/ext/MouseEventUI.java
  53. 644
      src/main/java/org/pbjar/jxlayer/plaf/ext/TransformUI.java
  54. 116
      src/main/java/org/pbjar/jxlayer/plaf/ext/transform/DefaultLayerLayout.java
  55. 495
      src/main/java/org/pbjar/jxlayer/plaf/ext/transform/DefaultTransformModel.java
  56. 140
      src/main/java/org/pbjar/jxlayer/plaf/ext/transform/TransformLayout.java
  57. 92
      src/main/java/org/pbjar/jxlayer/plaf/ext/transform/TransformModel.java
  58. 50
      src/main/java/org/pbjar/jxlayer/plaf/ext/transform/TransformRPMAnnotation.java
  59. 76
      src/main/java/org/pbjar/jxlayer/plaf/ext/transform/TransformRPMFallBack.java
  60. 197
      src/main/java/org/pbjar/jxlayer/plaf/ext/transform/TransformRPMImpl.java
  61. 76
      src/main/java/org/pbjar/jxlayer/plaf/ext/transform/TransformRPMSwingX.java
  62. 122
      src/main/java/org/pbjar/jxlayer/plaf/ext/transform/TransformUtils.java
  63. 76
      src/main/java/org/pbjar/jxlayer/repaint/RepaintManagerProvider.java
  64. 230
      src/main/java/org/pbjar/jxlayer/repaint/RepaintManagerUtils.java
  65. 228
      src/main/java/org/pbjar/jxlayer/repaint/WrappedRepaintManager.java
  66. 2
      src/main/resources/com/weis/darklaf/properties/ui/splitPane.properties
  67. 63
      src/main/resources/com/weis/darklaf/properties/ui/tabFrame.properties
  68. 6
      src/main/resources/com/weis/darklaf/properties/ui/tabbedPane.properties
  69. 6
      src/main/resources/com/weis/darklaf/theme/darcula/darcula_defaults.properties
  70. 2
      src/test/java/SplitPaneDemo.java
  71. 86
      src/test/java/TabFrameDemo.java

1
build.gradle

@ -20,6 +20,7 @@ dependencies {
compile 'com.metsci.ext.com.kitfox.svg:svg-salamander:[0.1.19,)' compile 'com.metsci.ext.com.kitfox.svg:svg-salamander:[0.1.19,)'
compile 'org.jetbrains:annotations:16.0.1' compile 'org.jetbrains:annotations:16.0.1'
compile 'net.java.dev.jna:jna:4.1.0' compile 'net.java.dev.jna:jna:4.1.0'
compile 'org.swinglabs:jxlayer:3.0.4'
compileOnly 'org.swinglabs:swingx:1.6.1' compileOnly 'org.swinglabs:swingx:1.6.1'
testCompile 'org.swinglabs:swingx:1.6.1' testCompile 'org.swinglabs:swingx:1.6.1'
} }

1
darklaf.iml

@ -4,6 +4,7 @@
<content url="file://$MODULE_DIR$"> <content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.gradle" /> <excludeFolder url="file://$MODULE_DIR$/.gradle" />
<excludeFolder url="file://$MODULE_DIR$/build" /> <excludeFolder url="file://$MODULE_DIR$/build" />
<excludeFolder url="file://$MODULE_DIR$/out" />
<excludeFolder url="file://$MODULE_DIR$/target" /> <excludeFolder url="file://$MODULE_DIR$/target" />
</content> </content>
<orderEntry type="inheritedJdk" /> <orderEntry type="inheritedJdk" />

104
src/main/java/com/weis/darklaf/components/Insets2D.java

@ -0,0 +1,104 @@
/*
* MIT License
*
* Copyright (c) 2019 Jannis Weis
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.weis.darklaf.components;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
public final class Insets2D implements Cloneable {
public double top;
public double left;
public double bottom;
public double right;
/**
* Creates and initializes a new <code>Insets</code> object with the
* specified top, left, bottom, and right insets.
*
* @param top the inset from the top.
* @param left the inset from the left.
* @param bottom the inset from the bottom.
* @param right the inset from the right.
*/
@Contract(pure = true)
public Insets2D(final double top, final double left, final double bottom, final double right) {
this.top = top;
this.left = left;
this.bottom = bottom;
this.right = right;
}
/**
* Set top, left, bottom, and right to the specified values
*
* @param top the inset from the top.
* @param left the inset from the left.
* @param bottom the inset from the bottom.
* @param right the inset from the right.
* @since 1.5
*/
public void set(final double top, final double left, final double bottom, final double right) {
this.top = top;
this.left = left;
this.bottom = bottom;
this.right = right;
}
@Contract(pure = true)
@Override
public int hashCode() {
double sum1 = left + bottom;
double sum2 = right + top;
double val1 = sum1 * (sum1 + 1) / 2 + left;
double val2 = sum2 * (sum2 + 1) / 2 + top;
double sum3 = val1 + val2;
return (int) (sum3 * (sum3 + 1) / 2 + val2);
}
@Contract(value = "null -> false", pure = true)
@Override
public boolean equals(final Object obj) {
if (obj instanceof Insets2D) {
Insets2D insets = (Insets2D) obj;
return ((top == insets.top) && (left == insets.left) &&
(bottom == insets.bottom) && (right == insets.right));
}
return false;
}
@NotNull
@Contract(value = " -> new", pure = true)
@Override
public Insets2D clone() {
return new Insets2D(top, left, bottom, right);
}
@NotNull
public String toString() {
return getClass().getName() + "[top=" + top + ",left=" + left + ",bottom=" + bottom + ",right=" + right + "]";
}
}

54
src/main/java/com/weis/darklaf/components/JLabelUIResource.java

@ -0,0 +1,54 @@
/*
* MIT License
*
* Copyright (c) 2019 Jannis Weis
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.weis.darklaf.components;
import javax.swing.*;
import javax.swing.plaf.UIResource;
public class JLabelUIResource extends JLabel implements UIResource {
public JLabelUIResource(final String text, final int horizontalAlignment) {
this(text, null, horizontalAlignment);
}
public JLabelUIResource(final String text, final Icon icon, final int horizontalAlignment) {
super(text, icon, horizontalAlignment);
}
public JLabelUIResource(final String text) {
this(text, null, LEADING);
}
public JLabelUIResource(final Icon image, final int horizontalAlignment) {
this(null, image, horizontalAlignment);
}
public JLabelUIResource(final Icon image) {
this(null, image, CENTER);
}
public JLabelUIResource() {
this("", null, LEADING);
}
}

47
src/main/java/com/weis/darklaf/components/JPanelUIResource.java

@ -0,0 +1,47 @@
/*
* MIT License
*
* Copyright (c) 2019 Jannis Weis
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.weis.darklaf.components;
import javax.swing.*;
import javax.swing.plaf.UIResource;
import java.awt.*;
public class JPanelUIResource extends JPanel implements UIResource {
public JPanelUIResource(final LayoutManager layout) {
this(layout, true);
}
public JPanelUIResource(final LayoutManager layout, final boolean isDoubleBuffered) {
super(layout, isDoubleBuffered);
}
public JPanelUIResource() {
this(true);
}
public JPanelUIResource(final boolean isDoubleBuffered) {
this(new FlowLayout(), isDoubleBuffered);
}
}

56
src/main/java/com/weis/darklaf/components/UIResourceWrapper.java

@ -0,0 +1,56 @@
/*
* MIT License
*
* Copyright (c) 2019 Jannis Weis
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.weis.darklaf.components;
import javax.swing.border.Border;
import java.awt.*;
public class UIResourceWrapper extends JPanelUIResource {
public UIResourceWrapper(final Component component) {
super(new BorderLayout());
super.setOpaque(false);
super.setBorder(null);
add(component, BorderLayout.CENTER);
}
@Override
public void setBorder(final Border border) {
super.setBorder(null);
}
@Override
public void setOpaque(final boolean isOpaque) {
}
@Override
public boolean isOpaque() {
return false;
}
@Override
public Border getBorder() {
return null;
}
}

14
src/main/java/com/weis/darklaf/components/border/MutableLineBorder.java

@ -57,4 +57,18 @@ public class MutableLineBorder extends EmptyBorder {
public void setBottom(final int bottom) { public void setBottom(final int bottom) {
this.bottom = bottom; this.bottom = bottom;
} }
public void setInsets(final int top, final int left, final int bottom, final int right) {
setTop(top);
setBottom(bottom);
setLeft(left);
setRight(right);
}
public static class UIResource extends MutableLineBorder implements javax.swing.plaf.UIResource {
public UIResource(final int top, final int left, final int bottom, final int right, final Color color) {
super(top, left, bottom, right, color);
}
}
} }

213
src/main/java/com/weis/darklaf/components/tabframe/PanelPopup.java

@ -0,0 +1,213 @@
/*
* MIT License
*
* Copyright (c) 2019 Jannis Weis
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.weis.darklaf.components.tabframe;
import com.weis.darklaf.components.alignment.Alignment;
import org.jetbrains.annotations.Contract;
import javax.swing.*;
import java.awt.*;
/**
* Popup Component for {@link TabFrame}.
*
* @author Jannis Weis
* @since 2019
*/
public class PanelPopup extends JPanel implements TabFramePopup {
private Component content;
private boolean open;
private TabFrame parent;
private String title;
private Icon icon;
private Alignment alignment;
private int index;
/**
* Creates a new Popup that holds one component.
*
* @param title the title of the component.
* @param content the content of the popup.
*/
public PanelPopup(final String title, final Component content) {
this(title, null, content);
}
/**
* Creates a new Popup that holds one component.
*
* @param title the title of the component.
* @param icon the icon of the popup.
* @param content the content of the popup.
*/
public PanelPopup(final String title, final Icon icon, final Component content) {
setIcon(icon);
setTitle(title);
setContentPane(content);
setEnabled(false);
}
@Override
public String getUIClassID() {
return "TabFramePanelPopupUI";
}
@Override
public Dimension getSize(final Dimension rv) {
if (!isEnabled()) {
return new Dimension(0, 0);
}
return super.getSize();
}
@Override
public int getWidth() {
if (!isEnabled()) {
return 0;
}
return super.getWidth();
}
@Override
public int getHeight() {
if (!isEnabled()) {
return 0;
}
return super.getHeight();
}
@Override
public Component getContentPane() {
if (content == null) {
setContentPane(null);
}
return content;
}
@Override
public void setContentPane(final Component component) {
var old = this.content;
this.content = component;
if (content == null) {
content = new JPanel();
}
firePropertyChange("content", old, content);
}
@Override
public Component getComponent() {
return this;
}
@Override
public void close() {
if (parent != null && getAlignment() != null && getIndex() >= 0) {
var oldOpen = isOpen();
parent.closeTab(getAlignment(), getIndex());
open = false;
firePropertyChange("open", oldOpen, false);
}
}
@Contract(pure = true)
private boolean isOpen() {
return open;
}
@Override
public TabFrame getTabFrame() {
return parent;
}
@Override
public void setTabFrame(final TabFrame parent) {
var old = this.parent;
this.parent = parent;
firePropertyChange("tabFrame", old, parent);
}
@Override
public Alignment getAlignment() {
return alignment;
}
@Override
public void setAlignment(final Alignment alignment) {
if (alignment == null || this.alignment == Alignment.CENTER) {
throw new IllegalArgumentException("Illegal alignment: " + (alignment != null
? alignment.toString() : "null"));
}
var old = this.alignment;
this.alignment = alignment;
firePropertyChange("alignment", old, alignment);
}
@Override
public void open() {
if (parent != null && getAlignment() != null && getIndex() >= 0) {
var oldOpen = isOpen();
parent.openTab(getAlignment(), getIndex());
open = true;
firePropertyChange("open", oldOpen, true);
requestFocus();
}
}
@Override
public String getTitle() {
return title;
}
@Override
public void setTitle(final String title) {
var old = this.title;
this.title = title == null ? "" : title;
firePropertyChange("title", old, this.title);
}
@Override
public Icon getIcon() {
return icon;
}
@Override
public void setIcon(final Icon icon) {
var old = this.icon;
this.icon = icon;
firePropertyChange("icon", old, icon);
}
@Override
public int getIndex() {
return index;
}
@Override
public void setIndex(final int index) {
int old = this.index;
this.index = index;
firePropertyChange("index", old, index);
}
}

67
src/main/java/com/weis/darklaf/components/tabframe/PopupContainer.java

@ -0,0 +1,67 @@
/*
* MIT License
*
* Copyright (c) 2019 Jannis Weis
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.weis.darklaf.components.tabframe;
import javax.swing.*;
import javax.swing.border.Border;
import java.awt.*;
/**
* Holder component.
*
* @author Jannis Weis
*/
public class PopupContainer extends JPanel {
private Component popup;
public PopupContainer() {
super(new BorderLayout());
super.setBorder(null);
}
public Component getPopup() {
return popup;
}
@Override
public void setBorder(final Border border) {
super.setBorder(null);
}
public void setPopup(final Component component) {
removeAll();
add(component, BorderLayout.CENTER);
this.popup = component;
revalidate();
repaint();
component.doLayout();
}
@Override
public Border getBorder() {
return null;
}
}

45
src/main/java/com/weis/darklaf/components/tabframe/TabArea.java

@ -0,0 +1,45 @@
/*
* MIT License
*
* Copyright (c) 2019 Jannis Weis
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.weis.darklaf.components.tabframe;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import java.awt.*;
public final class TabArea extends JPanel {
public TabArea() {
setLayout(null);
setOpaque(true);
}
@Override
public void paint(@NotNull final Graphics g) {
g.setColor(getBackground());
g.fillRect(0, 0, getWidth(), getHeight());
paintChildren(g);
paintBorder(g);
}
}

862
src/main/java/com/weis/darklaf/components/tabframe/TabFrame.java

@ -0,0 +1,862 @@
/*
* MIT License
*
* Copyright (c) 2019 Jannis Weis
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.weis.darklaf.components.tabframe;
import com.weis.darklaf.components.alignment.Alignment;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import javax.swing.plaf.ComponentUI;
import java.awt.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* Frame that supports popup components.
*
* @author Jannis Weis
*/
public class TabFrame extends JComponent {
private final JComponent bottomTabs = createTabContainer();
private final JComponent topTabs = createTabContainer();
private final JComponent leftTabs = createTabContainer();
private final JComponent rightTabs = createTabContainer();
private final TabFrameContent content = createContentPane();
private final List<TabFrameTab>[] tabLists;
private final List<TabFramePopup>[] popupLists;
private final int[] selectedIndices;
private int tabSize = -1;
private int maxTabWidth = -1;
/**
* Creates new {@link TabFrame}.
* A TabFrame displays one center component and multiple popups around
* that can be toggles with a TabbedPane like tabArea along the border.
*/
public TabFrame() {
super();
updateUI();
add(content.getComponent());
int count = Alignment.values().length;
//noinspection unchecked
tabLists = (ArrayList<TabFrameTab>[]) new ArrayList[count];
//noinspection unchecked
popupLists = (ArrayList<TabFramePopup>[]) new ArrayList[count];
for (int i = 0; i < count; i++) {
tabLists[i] = new ArrayList<>();
popupLists[i] = new ArrayList<>();
}
selectedIndices = new int[count];
}
@Override
public void updateUI() {
setUI(UIManager.getUI(this));
}
/**
* Get the ui.
*
* @return the ui.
*/
public TabFrameUI getUI() {
return (TabFrameUI) super.getUI();
}
@Override
protected void setUI(final ComponentUI newUI) {
if (!(newUI instanceof TabFrameUI)) {
throw new IllegalArgumentException("UI class must be of type TabFrameUI");
}
super.setUI(newUI);
}
@Override
public String getUIClassID() {
return "TabFrameUI";
}
/**
* Returns the height/width of the tab container.
*
* @return the tab size.
*/
public int getTabSize() {
return tabSize >= 0 ? tabSize : getUI().getTabSize(this);
}
/**
* Sets the height/width of the tab container.
* <p>
* A negative value means the ui should calculate the appropriate size.
*
* @param size the size of the tab container.
*/
public void setTabSize(final int size) {
this.tabSize = size;
}
/**
* Get the maximum width a tab can inhibit.
* A negative value indicated the tabs should use as much space as
* their proffered size indicates.
*
* @return the maximum tab width.
*/
public int getMaxTabWidth() {
return maxTabWidth;
}
/**
* Sets the maximum width a tab can inhibit.
* A negative value indicated the tabs should use as much space as
* their proffered size indicates.
*
* @param maxTabWidth the maximum tab width.
*/
public void setMaxTabWidth(final int maxTabWidth) {
this.maxTabWidth = maxTabWidth;
}
/**
* Get the number of tabs at the top.
*
* @return number of tabs at the top.
*/
public int getTopTabCount() {
return getTabCountAt(Alignment.NORTH) + getTabCountAt(Alignment.NORTH_EAST);
}
/**
* Get the number of tabs at the given alignment position.
*
* @param a the alignment position.
* @return number of tabs.
*/
public int getTabCountAt(final Alignment a) {
return tabsForAlignment(a).size();
}
/**
* Get a list of tab components at the given alignment position.
*
* @param a the alignment position.
* @return list of tab components at position.
*/
public List<TabFrameTab> tabsForAlignment(@NotNull final Alignment a) {
return tabLists[a.ordinal()];
}
/**
* Get the number of tabs at the bottom.
*
* @return number of tabs at the bottom.
*/
public int getBottomTabCount() {
return getTabCountAt(Alignment.SOUTH) + getTabCountAt(Alignment.SOUTH_WEST);
}
/**
* Get the number of tabs at the left.
*
* @return number of tabs at the left.
*/
public int getLeftTabCount() {
return getTabCountAt(Alignment.WEST) + getTabCountAt(Alignment.NORTH_WEST);
}
/**
* Get the number of tabs at the right.
*
* @return number of tabs at the right.
*/
public int getRightTabCount() {
return getTabCountAt(Alignment.EAST) + getTabCountAt(Alignment.SOUTH_EAST);
}
/**
* Get the center component.
*
* @return the center component.
*/
public Component getContent() {
return content.getContent();
}
/**
* Set the center component.
*
* @param c the center component.
*/
public void setContent(final Component c) {
content.setContent(c);
}
/**
* Get the content pane.
*
* @return the content pane.
*/
public TabFrameContent getContentPane() {
return content;
}
/**
* Create a tab container.
*
* @return a tab container.
*/
protected JComponent createTabContainer() {
return new TabArea();
}
/**
* Create the content pane.
*
* @return the content pane.
*/
protected TabFrameContent createContentPane() {
return new TabFrameContentPane();
}
/**
* Insert a tab.
* A default tab component and popup component will be created.
*
* @param c the component to add.
* @param a the alignment position to add at.{@link TabFramePosition#getAlignment()}
* @param index the index to insert at.{@link TabFramePosition#getIndex()}
*/
public void insertTab(@NotNull final Component c, final Alignment a, final int index) {
var title = c.getName();
insertTab(c, title == null ? "" : title, a, index);
}
/**
* Insert a tab.
* A default tab component and popup component will be created.
*
* @param c the component to add.
* @param title the title of the component.
* @param a the alignment position to add at.{@link TabFramePosition#getAlignment()}
* @param index the index to insert at.{@link TabFramePosition#getIndex()}
*/
public void insertTab(@NotNull final Component c, final String title, final Alignment a, final int index) {
insertTab(c, title, null, a, index);
}
/**
* Insert a tab.
* A default tab component and popup component will be created.
*
* @param c the component to add.
* @param title the title of the component.
* @param icon the icon
* @param a the alignment position to add at.{@link TabFramePosition#getAlignment()}
* @param index the index to insert at.{@link TabFramePosition#getIndex()}
*/
public void insertTab(@NotNull final Component c, final String title, final Icon icon, final Alignment a,
final int index) {
TabFramePopup popup = new PanelPopup(title, icon, c);
insertTab(popup, title, icon, a, index);
}
/**
* Insert a tab.
* A default tab component will be created.
*
* @param c the popup to add.
* @param title the title of the component.
* @param a the alignment position to add at.{@link TabFramePosition#getAlignment()}
* @param index the index to insert at.{@link TabFramePosition#getIndex()}
*/
public void insertTab(@NotNull final TabFramePopup c, final String title, final Icon icon, final Alignment a,
final int index) {
if (a == Alignment.CENTER) {
return;
}
var tabComponent = createDefaultTab(title, icon, a, index);
insertTabComp(tabComponent, a, index);
compsForAlignment(a).add(index, c);
c.setEnabled(false);
c.setTabFrame(this);
c.setAlignment(a);
c.setIndex(index);
}
protected TabFrameTab createDefaultTab(final String text, final Icon icon, final Alignment a, final int index) {
return new TabFrameTabLabel(text, icon, a, index, this);
}
/*
* Inserts a tab component at the given position.
*/
private void insertTabComp(@NotNull final TabFrameTab tabComp, final Alignment a, final int index) {
tabComp.setOrientation(a);
getTabContainer(a).add(tabComp.getComponent());
var tabs = tabsForAlignment(a);
//Adjust indices for tabs.
var iterator = tabs.listIterator(index);
while (iterator.hasNext()) {
var tab = iterator.next();
tab.setIndex(tab.getIndex() + 1);
}
tabComp.setIndex(index);
tabComp.setOrientation(a);
tabs.add(index, tabComp);
}
/**
* Get a list of components at the given alignment position.
*
* @param a the alignment position.
* @return list of components at position.
*/
public List<TabFramePopup> compsForAlignment(@NotNull final Alignment a) {
return popupLists[a.ordinal()];
}
/**
* Get the tab container for the given alignment position.
*
* @param a the alignment position.{@link TabFramePosition#getAlignment()}
* @return the tab container.
*/
@Contract(pure = true)
public JComponent getTabContainer(@NotNull final Alignment a) {
switch (a) {
case NORTH:
case NORTH_EAST:
return getTopTabContainer();
case SOUTH:
case SOUTH_WEST:
return getBottomTabContainer();
case EAST:
case SOUTH_EAST:
return getRightTabContainer();
case WEST:
case NORTH_WEST:
return getLeftTabContainer();
case CENTER:
throw new IllegalArgumentException("invalid alignment: " + a);
default:
throw new IllegalArgumentException();
}
}
/**
* Get the container that holds the top tab components.
*
* @return the top tab container.
*/
public JComponent getTopTabContainer() {
return topTabs;
}
/**
* Get the container that holds the bottom tab components.
*
* @return the bottom tab container.
*/
public JComponent getBottomTabContainer() {
return bottomTabs;
}
/**
* Get the container that holds the right tab components.
*
* @return the right tab container.
*/
public JComponent getRightTabContainer() {
return rightTabs;
}
/**
* Get the container that holds the left tab components.
*
* @return the left tab container.
*/
public JComponent getLeftTabContainer() {
return leftTabs;
}
/**
* Sets the popup at the given position.
*
* @param c the popup to place at the position.
* @param title the title of the popup.
* @param icon the icon of the popup.
* @param a the alignment position to place at.{@link TabFramePosition#getAlignment()}
* @param index the index to place at.{@link TabFramePosition#getIndex()}
*/
public void setTabAt(final TabFramePopup c, final String title, final Icon icon,
final Alignment a, final int index) {
if (a == Alignment.CENTER) {
return;
}
var text = title == null ? c.getComponent().getName() : title;
text = text == null ? c.getTitle() : text;
var tabComponent = createDefaultTab(text, icon, a, index);
c.setTitle(text);
c.setIcon(icon);
c.setTabFrame(this);
c.setAlignment(a);
c.setIndex(index);
tabComponent.setSelected(getTabComponentAt(a, index).isSelected());
setTabComponent(tabComponent, a, index);
compsForAlignment(a).set(index, c);
if (tabComponent.isSelected()) {
getComponentAt(a, index).doLayout();
getComponentAt(a, index).repaint();
}
}
/**
* Get the tab component at the given position.
*
* @param a the alignment position.{@link TabFramePosition#getAlignment()}
* @param index the index.{@link TabFramePosition#getIndex()}
* @return the tab component.
*/
public TabFrameTab getTabComponentAt(final Alignment a, final int index) {
var tabs = tabsForAlignment(a);
return tabs.get(index);
}
/*
* Set the tab component at the given position.
*/
private void setTabComponent(@NotNull final TabFrameTab tab, final Alignment a, final int index) {
var tabs = tabsForAlignment(a);
var oldComp = tabs.get(index);
getTabContainer(a).remove(oldComp.getComponent());
getTabContainer(a).add(tab.getComponent());
tabs.set(index, tab);
}
/**
* Get the component at the given position.
*
* @param a the alignment position.{@link TabFramePosition#getAlignment()}
* @param index the index. {@link TabFramePosition#getIndex()} ()}
* @return the popup component specified by {@link TabFramePopup#getContentPane()}.
*/
public Component getComponentAt(final Alignment a, final int index) {
var tabs = compsForAlignment(a);
return tabs.get(index).getContentPane();
}
/**
* Close a popup.
*
* @param c the component to close.
*/
public void closeTab(final Component c) {
var res = findComponent(c);
if (res != null) {
closeTab(res.getAlignment(), res.getIndex());
}
}
/**
* Gets the position of the given component or null if it isn't currently
* added.
*
* @param c the component to find.
* @return the position in the tabFrame.{@link TabFramePosition}
*/
public TabFramePosition findComponent(final Component c) {
for (var a : Alignment.values()) {
var list = popupLists[a.ordinal()];
for (int i = 0; i < list.size(); i++) {
if (Objects.equals(list.get(i).getContentPane(), c)) {
return new TabFramePosition(a, i);
}
}
}
return null;
}
/**
* Close a popup.
*
* @param a the alignment position of the popup.{@link TabFramePosition#getAlignment()}
* @param index the index of the tab.{@link TabFramePosition#getIndex()}
*/
public void closeTab(final Alignment a, final int index) {
toggleTab(a, index, false);
}
/**
* Toggles the visibility of a tab.
*
* @param a the alignment position.{@link TabFramePosition#getAlignment()}
* @param index the index.{@link TabFramePosition#getIndex()}
* @param enabled true if visible.
*/
public void toggleTab(@NotNull final Alignment a, final int index, final boolean enabled) {
var oldIndex = selectedIndices[a.getIndex()];
if (content.isEnabled(a) == enabled && oldIndex == index) return;
var compAtIndex = getTabComponentAt(a, index);
compAtIndex.setSelected(enabled);
notifySelectionChange(compAtIndex);
setPopupVisibility(compAtIndex, enabled);
if (enabled) {
getPopupComponentAt(a).doLayout();
getPopupComponentAt(a).requestFocus();
}
firePropertyChange("visibleTab", new TabFramePosition(a, oldIndex), new TabFramePosition(a, index));
}
/**
* Notify the tabFrame that a selection has changed.
*
* @param tabComponent the tab at which the selection has changed.
*/
public void notifySelectionChange(final TabFrameTab tabComponent) {
if (tabComponent == null) {
return;
}
var a = tabComponent.getOrientation();
selectedIndices[a.ordinal()] = tabComponent.getIndex();
if (tabComponent.isSelected()) {
for (var tc : tabsForAlignment(a)) {
if (tc != null && tc != tabComponent) {
tc.setSelected(false);
}
}
}
}
/*
* Set the visibility of the popup.
*/
private void setPopupVisibility(@NotNull final TabFrameTab tabComponent,
final boolean selected) {
var a = tabComponent.getOrientation();
var c = compsForAlignment(a).get(tabComponent.getIndex());
c.setEnabled(selected);
content.setComponentAt(a, c.getComponent());
content.setEnabled(a, selected);
doLayout();
}
/**
* Get the popup component at the given position that is currently active.
*
* @param a the alignment position. {@link TabFramePosition#getAlignment()}
* @return the popup component specified by {@link TabFramePopup#getComponent()}.
*/
public Component getPopupComponentAt(final Alignment a) {
var tabs = compsForAlignment(a);
return tabs.get(Math.max(Math.min(tabs.size() - 1, selectedIndices[a.ordinal()]), 0)).getComponent();
}
/**
* Close a popup.
*
* @param c the popup to close.
*/
public void closeTab(final TabFramePopup c) {
if (c == null) return;
closeTab(c.getAlignment(), c.getIndex());
}
/**
* Open a popup.
*
* @param c the component to open.
*/
public void openTab(final Component c) {
var res = findComponent(c);
if (res != null) {
openTab(res.getAlignment(), res.getIndex());
}
}
/**
* Open a popup.
*
* @param a the alignment position of the popup.{@link TabFramePosition#getAlignment()}
* @param index the index of the tab.{@link TabFramePosition#getIndex()}
*/
public void openTab(final Alignment a, final int index) {
toggleTab(a, index, true);
}
/**
* Open a popup.
*
* @param c the popup to open.
*/
public void openTab(final TabFramePopup c) {
if (c == null) return;
openTab(c.getAlignment(), c.getIndex());
}
/**
* Add a popup.
*
* @param c the content component.
* @param title the title.
* @param a the alignment position.{@link TabFramePosition#getAlignment()}
*/
public void addTab(final Component c, final String title, final Alignment a) {
addTab(c, title, null, a);
}
/**
* Add a popup.
*
* @param c the content component.
* @param title the title.
* @param icon the icon.
* @param a the alignment position.{@link TabFramePosition#getAlignment()}
*/
public void addTab(final Component c, final String title, final Icon icon, final Alignment a) {
insertTab(c, title, icon, a, tabsForAlignment(a).size());
}
/**
* Add a popup.
*
* @param c the popup.
* @param title the title.
* @param icon the icon.
* @param a the alignment position.{@link TabFramePosition#getAlignment()}
*/
public void addTab(final TabFramePopup c, final String title, final Icon icon, final Alignment a) {
insertTab(c, title, icon, a, tabsForAlignment(a).size());
}
/**
* Remove a popup.
*
* @param a the alignment position.{@link TabFramePosition#getAlignment()}
* @param index the index of the tab.{@link TabFramePosition#getIndex()}
*/
public void removeTab(final Alignment a, final int index) {
var comp = compsForAlignment(a).get(index);
comp.close();
comp.setTabFrame(null);
comp.setIndex(-1);
removeTabComp(a, index);
doLayout();
getTabContainer(a).repaint();
}
/**
* Move a tab to a new position.
*
* @param tabComp the tab to move.
* @param a the new alignment position.{@link TabFramePosition#getAlignment()}
*/
public void moveTab(@NotNull final TabFrameTab tabComp, final Alignment a) {
if (a == tabComp.getOrientation()) {
return;
}
boolean oldSelected = tabComp.isSelected();
var oldAlign = tabComp.getOrientation();
int index = tabComp.getIndex();
compsForAlignment(oldAlign).get(index).close();
removeTabComp(oldAlign, index);
var comp = compsForAlignment(oldAlign).remove(index);
int newIndex = tabsForAlignment(a).size();
insertTabComp(tabComp, a, newIndex);
compsForAlignment(a).add(newIndex, comp);
tabComp.setSelected(oldSelected);
notifySelectionChange(tabComp);
comp.setIndex(newIndex);
comp.setAlignment(a);
doLayout();
getTabContainer(oldAlign).repaint();
getTabContainer(a).repaint();
}
/*
* Remove a tab component from the given position.
*/
private void removeTabComp(final Alignment a, final int index) {
var tabs = tabsForAlignment(a);
//Adjust indices for tabs.
var iterator = tabs.listIterator(index);
while (iterator.hasNext()) {
var tab = iterator.next();
tab.setIndex(tab.getIndex() - 1);
}
var tab = tabs.remove(index);
getTabContainer(a).remove(tab.getComponent());
}
/**
* Get the popup component at the given position.
*
* @param a the alignment position.{@link TabFramePosition#getAlignment()}
* @param index the index.{@link TabFramePosition#getIndex()}
* @return the popup component specified by {@link TabFramePopup#getComponent()}.
*/
public Component getPopupComponentAt(final Alignment a, final int index) {
var tabs = compsForAlignment(a);
return tabs.get(index).getComponent();
}
/**
* Get the component at the given position.
*
* @param a the alignment position. {@link TabFramePosition#getAlignment()}
* @return the component specified by {@link TabFramePopup#getContentPane()}.
*/
public Component getComponentAt(final Alignment a) {
var tabs = compsForAlignment(a);
return tabs.get(Math.max(Math.min(tabs.size() - 1, selectedIndices[a.ordinal()]), 0)).getContentPane();
}
/**
* Get the custom tab component at the given position.
*
* @param a the alignment position.{@link TabFramePosition#getAlignment()}
* @param index the index.{@link TabFramePosition#getIndex()}
* @return the user tab component or null if none is installed.
*/
public Component getUserTabComponentAt(final Alignment a, final int index) {
var tab = getTabComponentAt(a, index);
if (tab instanceof TabFrameTabContainer) {
return ((TabFrameTabContainer) tab).getContent();
} else {
return null;
}
}
/**
* Set the custom tab component at the given position.
*
* @param component the custom tab component.
* @param a the alignment position.{@link TabFramePosition#getAlignment()}
* @param index the index.{@link TabFramePosition#getIndex()}
*/
public void setUserTabComponentAt(final JComponent component, final Alignment a, final int index) {
var tabs = tabsForAlignment(a);
var tabContainer = tabs.get(index);
if (component == null) {
if (tabContainer instanceof TabFrameTabContainer) {
setTabComponent(((TabFrameTabContainer) tabContainer).oldTab, a, index);
}
} else {
if (tabContainer instanceof TabFrameTabContainer) {
((TabFrameTabContainer) tabContainer).setContent(component);
} else {
var cont = new TabFrameTabContainer(this, component, tabContainer, a, index);
setTabComponent(cont, a, index);
}
}
}
/**
* Set the accelerator index at the given position.
*
* @param accelerator the accelerator. (a negative value being no accelerator).
* @param a the alignment position.{@link TabFramePosition#getAlignment()}
* @param index the index.{@link TabFramePosition#getIndex()}
*/
public void setAcceleratorAt(final int accelerator, final Alignment a, final int index) {
getTabComponentAt(a, index).setAccelerator(accelerator);
}
/**
* Get the position of the alignment peer. That being the
* other position that occupies the same tab container given by
* {@link #getTabContainer(Alignment)}.
* <p>
* NORTH <--> NORTH_EAST
* <p>
* EAST <--> SOUTH_EAST
* <p>
* SOUTH <--> SOUTH_WEST
* <p>
* WEST <--> NORTH_WEST
*
* @param a the alignment position.{@link TabFramePosition#getAlignment()}
* @return the peer position.{@link TabFramePosition#getAlignment()}
*/
public Alignment getPeer(@NotNull final Alignment a) {
switch (a) {
case NORTH:
case SOUTH:
case WEST:
case EAST:
return a.clockwise();
case NORTH_EAST:
case NORTH_WEST:
case SOUTH_EAST:
case SOUTH_WEST:
return a.anticlockwise();
}
return a;
}
/**
* This class represents a position inside the tabFrame.
*/
public static class TabFramePosition {
private final Alignment a;
private final int index;
@Contract(pure = true)
public TabFramePosition(final Alignment a, final int index) {
this.a = a;
this.index = index;
}
/**
* The alignment position.
* This specifies at what location the tab is placed.
*
* @return the alignment position.
*/
public Alignment getAlignment() {
return a;
}
/**
* The index inside the alignment position.
*
* @return the index.
*/
public int getIndex() {
return index;
}
}
}

83
src/main/java/com/weis/darklaf/components/tabframe/TabFrameContent.java

@ -0,0 +1,83 @@
/*
* MIT License
*
* Copyright (c) 2019 Jannis Weis
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.weis.darklaf.components.tabframe;
import com.weis.darklaf.components.alignment.Alignment;
import java.awt.*;
public interface TabFrameContent {
/**
* Get the current component displayed in the middle.
*
* @return the content component.
*/
Component getContent();
/**
* Sets the content to displayed in the middle.
*
* @param content the content component.
*/
void setContent(final Component content);
/**
* Enables a popup if possible.
*
* @param a the alignment.
* @param enabled true if enabled.
*/
void setEnabled(Alignment a, boolean enabled);
/**
* Returns whether the given popup is enabled.
*
* @param a the alignment of the popup.
* @return true if enabled.
*/
boolean isEnabled(Alignment a);
/**
* Sets the component at the specified position.
*
* @param a the alignment.
* @param component the component.
*/
void setComponentAt(final Alignment a, final Component component);
/**
* Get the component that displays the content.
*
* @return the display component.
*/
Component getComponent();
/**
* Returns an array with the enabled status of each alignment.
*
* @return the enabled status.
*/
boolean[] getStatus();
}

449
src/main/java/com/weis/darklaf/components/tabframe/TabFrameContentPane.java

@ -0,0 +1,449 @@
/*
* MIT License
*
* Copyright (c) 2019 Jannis Weis
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.weis.darklaf.components.tabframe;
import com.weis.darklaf.components.alignment.Alignment;
import com.weis.darklaf.decorators.AncestorAdapter;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import javax.swing.event.AncestorEvent;
import java.awt.*;
import java.util.function.BiConsumer;
/**
* Content pane for {@link TabFrame}.
*
* @author Jannis Weis
*/
public class TabFrameContentPane extends JPanel implements TabFrameContent {
private static final double HORIZONTAL_PROP_LEFT = 0.2;
private static final double VERTICAL_PROP_TOP = 0.2;
private static final double VERTICAL_PROP_BOTTOM = 0.75;
private static final double HORIZONTAL_PROP_RIGHT = 0.75;
private final ToggleSplitPane topSplit;
private final ToggleSplitPane bottomSplit;
private final ToggleSplitPane leftSplit;
private final ToggleSplitPane rightSplit;
private final ToggleSplitPane leftSplitter;
private final ToggleSplitPane rightSplitter;
private final ToggleSplitPane topSplitter;
private final ToggleSplitPane bottomSplitter;
private final boolean[] enabled = new boolean[8];
private Component cont = new JPanel();
public TabFrameContentPane() {
super(new BorderLayout());
cont.setBackground(Color.YELLOW);
PopupContainer leftBottomPanel = new PopupContainer();
PopupContainer rightTopPanel = new PopupContainer();
PopupContainer rightBottomPanel = new PopupContainer();
PopupContainer topLeftPanel = new PopupContainer();
PopupContainer topRightPanel = new PopupContainer();
PopupContainer bottomLeftPanel = new PopupContainer();
PopupContainer bottomRightPanel = new PopupContainer();
PopupContainer leftTopPanel = new PopupContainer();
rightSplitter = new ToggleSplitPane("rightSplitter");
rightSplitter.setOrientation(JSplitPane.VERTICAL_SPLIT);
rightSplitter.setTopComponent(rightTopPanel);
rightSplitter.setBottomComponent(rightBottomPanel);
leftSplitter = new ToggleSplitPane("leftSplitter");
leftSplitter.setOrientation(JSplitPane.VERTICAL_SPLIT);
leftSplitter.setTopComponent(leftTopPanel);
leftSplitter.setBottomComponent(leftBottomPanel);
topSplitter = new ToggleSplitPane("topSplitter");
topSplitter.setOrientation(JSplitPane.HORIZONTAL_SPLIT);
topSplitter.setLeftComponent(topLeftPanel);
topSplitter.setRightComponent(topRightPanel);
bottomSplitter = new ToggleSplitPane("bottomSplitter");
bottomSplitter.setOrientation(JSplitPane.HORIZONTAL_SPLIT);
bottomSplitter.setLeftComponent(bottomLeftPanel);
bottomSplitter.setRightComponent(bottomRightPanel);
topSplit = new ToggleSplitPane("topSplit");
bottomSplit = new ToggleSplitPane("bottomSplit");
topSplit.setOrientation(JSplitPane.VERTICAL_SPLIT);
bottomSplit.setOrientation(JSplitPane.VERTICAL_SPLIT);
topSplit.setTopComponent(topSplitter);
topSplit.setBottomComponent(bottomSplit);
bottomSplit.setBottomComponent(bottomSplitter);
leftSplit = new ToggleSplitPane("leftSplit");
rightSplit = new ToggleSplitPane("rightSplit");
leftSplit.setOrientation(JSplitPane.HORIZONTAL_SPLIT);
rightSplit.setOrientation(JSplitPane.HORIZONTAL_SPLIT);
bottomSplit.setTopComponent(leftSplit);
leftSplit.setLeftComponent(leftSplitter);
leftSplit.setRightComponent(rightSplit);
rightSplit.setRightComponent(rightSplitter);
rightSplit.setLeftComponent(cont);
topSplit.setResizeWeight(0.0d);
leftSplit.setResizeWeight(0.0d);
bottomSplit.setResizeWeight(1.0d);
rightSplit.setResizeWeight(1.0d);
setupSplitterPanes(JSplitPane::setResizeWeight, 0.5d);
setupSplitPanes(JSplitPane::setEnabled, false);
setupSplitPanes(ToggleSplitPane::setResizable, false);
setupSplitterPanes(JSplitPane::setEnabled, false);
setupSplitterPanes(ToggleSplitPane::setResizable, false);
add(topSplit, BorderLayout.CENTER);
addAncestorListener(new AncestorAdapter() {
@Override
public void ancestorAdded(final AncestorEvent event) {
removeAncestorListener(this);
SwingUtilities.invokeLater(() -> init());
}
});
}
private <T> void setupSplitterPanes(@NotNull final BiConsumer<? super ToggleSplitPane, T> consumer,
final T flag) {
consumer.accept(bottomSplitter, flag);
consumer.accept(leftSplitter, flag);
consumer.accept(rightSplitter, flag);
consumer.accept(topSplitter, flag);
}
private <T> void setupSplitPanes(@NotNull final BiConsumer<? super ToggleSplitPane, T> consumer,
final T flag) {
consumer.accept(topSplit, flag);
consumer.accept(leftSplit, flag);
consumer.accept(bottomSplit, flag);
consumer.accept(rightSplit, flag);
}
private void init() {
disableAll(true);
topSplit.savePosition(VERTICAL_PROP_TOP);
bottomSplit.savePosition(VERTICAL_PROP_BOTTOM);
leftSplit.savePosition(HORIZONTAL_PROP_LEFT);
rightSplit.savePosition(HORIZONTAL_PROP_RIGHT);
setupSplitterPanes(ToggleSplitPane::savePosition, 0.5d);
doLayout();
}
public void disableAll(final boolean force) {
for (var a : Alignment.values()) {
setEnabled(a, false, force);
}
}
/**
* Show or hide the corresponding panel.
*
* @param a position of panel.
* @param enabled true if should be shown.
* @param force whether to force the layout process.
*/
public void setEnabled(@NotNull final Alignment a, final boolean enabled, final boolean force) {
if (enabled == isEnabled(a) && !force) return;
switch (a) {
case NORTH:
changeStatus(
enabled, Alignment.NORTH_EAST,
topSplit, topSplitter,
new LayoutProportions(VERTICAL_PROP_TOP, 1.0, 0.0, 0.0),
new LayoutWeights(0.0, 0.0, 0.0, 1.0));
break;
case NORTH_EAST:
changeStatus(
enabled, Alignment.NORTH,
topSplit, topSplitter,
new LayoutProportions(VERTICAL_PROP_TOP, 0.0, 0.0, 1.0),
new LayoutWeights(0.0, 0.0, 1.0, 0.0));
break;
case EAST:
changeStatus(
enabled, Alignment.SOUTH_EAST,
rightSplit, rightSplitter,
new LayoutProportions(HORIZONTAL_PROP_RIGHT, 1.0, 1.0, 0.0),
new LayoutWeights(1.0, 1.0, 0.0, 1.0));
break;
case SOUTH_EAST:
changeStatus(
enabled, Alignment.EAST,
rightSplit, rightSplitter,
new LayoutProportions(HORIZONTAL_PROP_RIGHT, 0.0, 1.0, 1.0),
new LayoutWeights(1.0, 1.0, 1.0, 0.0));
break;
case NORTH_WEST:
changeStatus(
enabled, Alignment.WEST,
leftSplit, leftSplitter,
new LayoutProportions(VERTICAL_PROP_TOP, 1.0, 0.0, 0.0),
new LayoutWeights(0.0, 0.0, 0.0, 1.0));
break;
case WEST:
changeStatus(
enabled, Alignment.NORTH_WEST,
leftSplit, leftSplitter,
new LayoutProportions(VERTICAL_PROP_TOP, 0.0, 0.0, 1.0),
new LayoutWeights(0.0, 0.0, 1.0, 0.0));
break;
case SOUTH_WEST:
changeStatus(
enabled, Alignment.SOUTH,
bottomSplit, bottomSplitter,
new LayoutProportions(VERTICAL_PROP_BOTTOM, 1.0, 1.0, 0.0),
new LayoutWeights(1.0, 1.0, 0.0, 1.0));
break;
case SOUTH:
changeStatus(
enabled, Alignment.SOUTH_WEST,
bottomSplit, bottomSplitter,
new LayoutProportions(VERTICAL_PROP_BOTTOM, 0.0, 1.0, 1.0),
new LayoutWeights(1.0, 1.0, 1.0, 0.0));
break;
case CENTER:
break;
}
setEnabledFlag(a, enabled);
}
/**
* Change status of panel.
*
* @param enabled new status.
* @param peer peer alignment.
* @param split the split panel.
* @param splitter the splitter panel.
* @param proportions the layout proportions
* @param weights the layout weights
*/
private void changeStatus(final boolean enabled, @NotNull final Alignment peer,
@NotNull final ToggleSplitPane split,
@NotNull final ToggleSplitPane splitter,
@NotNull final LayoutProportions proportions,
@NotNull final LayoutWeights weights) {
if (enabled) {
enable(split, weights.splitEnable);
if (!isEnabled(peer)) {
disable(splitter, weights.splitterPeerDisable, proportions.splitterPeerDisable);
} else {
enable(splitter, 0.5d);
}
} else {
if (!isEnabled(peer)) {
disable(split, weights.splitDisable, proportions.splitDisable);
}
disable(splitter, weights.splitterDisable, proportions.splitterDisable);
}
}
/*
* Update the flags.
*/
private void setEnabledFlag(@NotNull final Alignment a, final boolean e) {
if (a != Alignment.CENTER) {
enabled[a.getIndex()] = e;
}
}
private void enable(@NotNull final ToggleSplitPane splitPane, final double weight) {
boolean restore = !splitPane.isResizable();
splitPane.setEnabled(true);
splitPane.setResizable(true);
splitPane.setResizeWeight(weight);
if (restore) {
splitPane.restorePosition();
}
}
private void disable(@NotNull final ToggleSplitPane splitPane, final double weight, final double location) {
if (splitPane.isResizable()) {
splitPane.savePosition();
}
splitPane.setResizeWeight(weight);
splitPane.forceSetDividerLocation(location);
splitPane.setEnabled(false);
splitPane.setResizable(false);
}
public Component getContent() {
return cont;
}
public void setContent(final Component pane) {
cont = pane;
rightSplit.setLeftComponent(pane);
}
/**
* Show or hide the corresponding panel.
*
* @param a position of panel.
* @param enabled true if should be shown.
*/
public void setEnabled(@NotNull final Alignment a, final boolean enabled) {
setEnabled(a, enabled, false);
}
/**
* Returns whether the corresponding panel is currently enabled/visible.
*
* @param a the position of the panel.
* @return true if enabled.
*/
public boolean isEnabled(@NotNull final Alignment a) {
if (a == Alignment.CENTER) {
return false;
} else {
return enabled[a.getIndex()];
}
}
public void setComponentAt(@NotNull final Alignment a, final Component c) {
switch (a) {
case NORTH:
((PopupContainer) topSplitter.getLeftComponent()).setPopup(c);
break;
case NORTH_EAST:
((PopupContainer) topSplitter.getRightComponent()).setPopup(c);
break;
case EAST:
((PopupContainer) rightSplitter.getTopComponent()).setPopup(c);
break;
case SOUTH_EAST:
((PopupContainer) rightSplitter.getBottomComponent()).setPopup(c);
break;
case SOUTH:
((PopupContainer) bottomSplitter.getRightComponent()).setPopup(c);
break;
case SOUTH_WEST:
((PopupContainer) bottomSplitter.getLeftComponent()).setPopup(c);
break;
case WEST:
((PopupContainer) leftSplitter.getBottomComponent()).setPopup(c);
break;
case NORTH_WEST:
((PopupContainer) leftSplitter.getTopComponent()).setPopup(c);
break;
case CENTER:
break;
}
}
@Override
public Component getComponent() {
return this;
}
/**
* Get the status of the individual panels.
*
* @return array of status of panels.
*/
@NotNull
public boolean[] getStatus() {
return enabled;
}
/**
* Get the popup component at the position.
*
* @param a the position.
* @return the popup component at position.
*/
@NotNull
public PanelPopup getPopupComponent(@NotNull final Alignment a) {
Component popupComponent;
switch (a) {
case NORTH:
popupComponent = ((PopupContainer) topSplitter.getLeftComponent()).getPopup();
break;
case NORTH_EAST:
popupComponent = ((PopupContainer) topSplitter.getRightComponent()).getPopup();
break;
case EAST:
popupComponent = ((PopupContainer) rightSplitter.getTopComponent()).getPopup();
break;
case SOUTH_EAST:
popupComponent = ((PopupContainer) rightSplitter.getBottomComponent()).getPopup();
break;
case SOUTH:
popupComponent = ((PopupContainer) bottomSplitter.getRightComponent()).getPopup();
break;
case SOUTH_WEST:
popupComponent = ((PopupContainer) bottomSplitter.getLeftComponent()).getPopup();
break;
case WEST:
popupComponent = ((PopupContainer) leftSplitter.getBottomComponent()).getPopup();
break;
case NORTH_WEST:
popupComponent = ((PopupContainer) leftSplitter.getTopComponent()).getPopup();
break;
default:
throw new IllegalArgumentException("CENTER is not supported");
}
return (PanelPopup) popupComponent;
}
protected static class LayoutProportions {
protected final double splitRestore;
protected final double splitterPeerDisable;
protected final double splitDisable;
protected final double splitterDisable;
@Contract(pure = true)
public LayoutProportions(final double splitRestore, final double splitterPeerDisable,
final double splitDisable, final double splitterDisable) {
this.splitRestore = splitRestore;
this.splitterPeerDisable = splitterPeerDisable;
this.splitDisable = splitDisable;
this.splitterDisable = splitterDisable;
}
}
protected static class LayoutWeights {
protected final double splitEnable;
protected final double splitterDisable;
protected final double splitDisable;
protected final double splitterPeerDisable;
@Contract(pure = true)
public LayoutWeights(final double splitEnable, final double splitterDisable,
final double splitDisable, final double splitterPeerDisable) {
this.splitEnable = splitEnable;
this.splitterDisable = splitterDisable;
this.splitDisable = splitDisable;
this.splitterPeerDisable = splitterPeerDisable;
}
}
}

158
src/main/java/com/weis/darklaf/components/tabframe/TabFramePopup.java

@ -0,0 +1,158 @@
/*
* MIT License
*
* Copyright (c) 2019 Jannis Weis
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.weis.darklaf.components.tabframe;
import com.weis.darklaf.components.alignment.Alignment;
import javax.swing.*;
import java.awt.*;
public interface TabFramePopup {
/**
* Get the current content pane. A popup may hold more
* than one content pane. In this case this method should return
* the component that is currently displayed.
*
* @return the current content pane.
*/
Component getContentPane();
/**
* Sets the content pane. This needn't replace the old
* content pane if this popup supports multiple content panes.
*/
void setContentPane(Component contentPane);
/**
* Get the component that realizes this popup.
*
* @return the component.
*/
Component getComponent();
/**
* Close the popup.
*/
default void close() {
if (getTabFrame() != null && getAlignment() != null && getIndex() >= 0) {
getTabFrame().closeTab(getAlignment(), getIndex());
}
}
/**
* Get the {{@link TabFrame}} this popup belongs to.
*
* @return the {{@link TabFrame}}.
*/
TabFrame getTabFrame();
/**
* Sets the {{@link TabFrame}} this popup belongs to.
*
* @param tabFrame the {{@link TabFrame}}.
*/
void setTabFrame(TabFrame tabFrame);
/**
* Gets the alignment position in the {{@link TabFrame}}.
*
* @return the alignment position.
*/
Alignment getAlignment();
/**
* Get the index of the popup.
*
* @return the index.
*/
int getIndex();
/**
* Set the index of the popup.
* This method should only be called from {{@link TabFrame}}.
*
* @param index the index.
*/
void setIndex(int index);
/**
* Sets the alignment position in the {{@link TabFrame}}.
* This method should only be called from {{@link TabFrame}}.
*
* @param alignment the alignment position.
*/
void setAlignment(Alignment alignment);
/**
* Open the popup.
*/
default void open() {
if (getTabFrame() != null && getAlignment() != null && getIndex() >= 0) {
getTabFrame().closeTab(getAlignment(), getIndex());
}
}
/**
* Returns whether this popup is enabled.
*
* @return true if enabled.
*/
boolean isEnabled();
/**
* Sets the enabled status of the popup.
*
* @param enabled true if enabled.
*/
void setEnabled(boolean enabled);
/**
* Get the title of the popup.
*
* @return the title.
*/
String getTitle();
/**
* Set the title of the popup.
*
* @param title the title.
*/
void setTitle(String title);
/**
* Get the icon of the popup.
*
* @return the icon.
*/
Icon getIcon();
/**
* Set the icon of the popup.
*
* @param icon the icon.
*/
void setIcon(Icon icon);
}

108
src/main/java/com/weis/darklaf/components/tabframe/TabFrameTab.java

@ -0,0 +1,108 @@
/*
* MIT License
*
* Copyright (c) 2019 Jannis Weis
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.weis.darklaf.components.tabframe;
import com.weis.darklaf.components.alignment.Alignment;
import java.awt.*;
public interface TabFrameTab {
/**
* Get the index of the tab.
*
* @return the current index.
*/
int getIndex();
/**
* Sets the index of the tab.
*
* @param index the index.
*/
void setIndex(int index);
/**
* Get the component housing the tab.
*
* @return the display component.
*/
Component getComponent();
/**
* Get the orientation.
*
* @return the orientation.
*/
Alignment getOrientation();
/**
* Sets the orientation.
*
* @param a the orientation.
*/
void setOrientation(Alignment a);
/**
* Returns whether the tab is selected.
*
* @return true if selected.
*/
boolean isSelected();
/**
* Set the selected status of the tab.
*
* @param selected true if selected
*/
void setSelected(boolean selected);
/**
* Get the accelerator.
*
* @return the accelerator.
*/
int getAccelerator();
/**
* Set the accelerator.
*
* @param accelerator accelerator (>0).
*/
void setAccelerator(int accelerator);
/**
* Get the tab frame this tab currently belongs to.
*
* @return the TabFrame.
*/
TabFrame getTabFrame();
/**
* Set the tab frame this tab currently belongs to.
*
* @param tabFrame the TabFrame.
*/
void setTabFrame(TabFrame tabFrame);
}

153
src/main/java/com/weis/darklaf/components/tabframe/TabFrameTabContainer.java

@ -0,0 +1,153 @@
/*
* MIT License
*
* Copyright (c) 2019 Jannis Weis
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.weis.darklaf.components.tabframe;
import com.weis.darklaf.components.alignment.Alignment;
import javax.swing.*;
import java.awt.*;
public class TabFrameTabContainer extends JPanel implements TabFrameTab {
protected final TabFrameTab oldTab;
private TabFrame parent;
private Component content;
private Alignment orientation;
private boolean selected;
private int index;
private int accelerator;
public TabFrameTabContainer(final TabFrame parent, final JComponent content, final TabFrameTab oldTab,
final Alignment alignment, final int index) {
super(new BorderLayout());
this.parent = parent;
this.oldTab = oldTab;
this.accelerator = -1;
setOpaque(false);
setOrientation(alignment);
setIndex(index);
setContent(content);
}
@Override
public String getUIClassID() {
return "TabFrameTabContainerUI";
}
/**
* Get the content component.
*
* @return the content component.
*/
public Component getContent() {
return content;
}
/**
* Set the content to display.
*
* @param content the content component.
*/
public void setContent(final JComponent content) {
var old = this.content;
remove(content);
this.content = content;
add(content, BorderLayout.CENTER);
firePropertyChange("content", old, content);
}
@Override
public Color getBackground() {
if (content != null && !content.isOpaque()) {
return super.getBackground();
}
return content != null ? content.getBackground() : super.getBackground();
}
@Override
public int getIndex() {
return index;
}
@Override
public void setIndex(final int index) {
this.index = index;
}
@Override
public Component getComponent() {
return this;
}
@Override
public Alignment getOrientation() {
return orientation;
}
@Override
public void setOrientation(final Alignment a) {
if (this.orientation == a) return;
var oldOrientation = this.orientation;
this.orientation = a;
firePropertyChange("orientation", oldOrientation, orientation);
}
@Override
public boolean isSelected() {
return selected;
}
@Override
public void setSelected(final boolean selected) {
if (selected == this.selected) return;
boolean oldSelected = this.selected;
this.selected = selected;
firePropertyChange("selected", oldSelected, selected);
}
@Override
public int getAccelerator() {
return accelerator;
}
@Override
public void setAccelerator(final int accelerator) {
if (this.accelerator == accelerator) return;
int oldAccelerator = this.accelerator;
this.accelerator = accelerator;
firePropertyChange("accelerator", oldAccelerator, accelerator);
}
@Override
public TabFrame getTabFrame() {
return parent;
}
@Override
public void setTabFrame(final TabFrame parent) {
var old = this.parent;
this.parent = parent;
firePropertyChange("tabFrame", old, parent);
}
}

167
src/main/java/com/weis/darklaf/components/tabframe/TabFrameTabLabel.java

@ -0,0 +1,167 @@
/*
* MIT License
*
* Copyright (c) 2019 Jannis Weis
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.weis.darklaf.components.tabframe;
import com.weis.darklaf.components.alignment.Alignment;
import com.weis.darklaf.icons.EmptyIcon;
import com.weis.darklaf.ui.tabframe.DarkTabFrameTabLabelUI;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
import java.util.Objects;
/**
* Tab Component for {@link TabFrame}.
*
* @author Jannis Weis
*/
public class TabFrameTabLabel extends JLabel implements TabFrameTab {
private TabFrame parent;
private Alignment orientation;
private String title;
private boolean selected;
private int accelerator;
private int index;
/**
* Create new TabComponent for the frame of {@link TabFrame}.
*
* @param title the title.
* @param icon the icon.
* @param orientation the alignment.
* @param index the index.
* @param parent the parent layout manager.
*/
public TabFrameTabLabel(final String title, final Icon icon, final Alignment orientation,
final int index, @NotNull final TabFrame parent) {
this.index = index;
this.accelerator = -1;
this.parent = parent;
setOrientation(orientation);
setIcon(icon == null ? EmptyIcon.create(0) : icon);
setTitle(title);
setText(title);
}
@Override
public String getUIClassID() {
return "TabFrameTabLabelUI";
}
public void setUI(final DarkTabFrameTabLabelUI ui) {
super.setUI(ui);
}
@Override
public int getIndex() {
return index;
}
@Override
public void setIndex(final int index) {
this.index = index;
}
@Override
public Component getComponent() {
return this;
}
@Override
public Alignment getOrientation() {
return orientation;
}
@Override
public void setOrientation(final Alignment a) {
if (this.orientation == a) return;
var oldOrientation = this.orientation;
this.orientation = a;
firePropertyChange("orientation", oldOrientation, orientation);
}
@Override
public boolean isSelected() {
return selected;
}
@Override
public void setSelected(final boolean selected) {
if (selected == this.selected) return;
boolean oldSelected = this.selected;
this.selected = selected;
firePropertyChange("selected", oldSelected, selected);
}
@Override
public int getAccelerator() {
return accelerator;
}
@Override
public void setAccelerator(final int accelerator) {
if (this.accelerator == accelerator) return;
int oldAccelerator = this.accelerator;
this.accelerator = accelerator;
firePropertyChange("accelerator", oldAccelerator, accelerator);
}
@Override
public TabFrame getTabFrame() {
return parent;
}
@Override
public void setTabFrame(final TabFrame parent) {
var old = this.parent;
this.parent = parent;
firePropertyChange("tabFrame", old, parent);
}
/**
* Returns the current title.
* This needn't be the same as the displayed text.
* For this use {@link #getText()} instead.
*
* @return the current title.
*/
public String getTitle() {
return title;
}
/**
* Set the title of the component.
*
* @param title the title
*/
public void setTitle(@Nullable final String title) {
if (Objects.equals(title, this.title)) return;
String oldTitle = this.title;
this.title = title;
firePropertyChange("title", oldTitle, selected);
}
}

31
src/main/java/com/weis/darklaf/components/tabframe/TabFrameUI.java

@ -0,0 +1,31 @@
/*
* MIT License
*
* Copyright (c) 2019 Jannis Weis
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.weis.darklaf.components.tabframe;
import javax.swing.plaf.ComponentUI;
public abstract class TabFrameUI extends ComponentUI {
public abstract int getTabSize(TabFrame tabFrame);
}

154
src/main/java/com/weis/darklaf/components/tabframe/TabbedPopup.java

@ -0,0 +1,154 @@
/*
* MIT License
*
* Copyright (c) 2019 Jannis Weis
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.weis.darklaf.components.tabframe;
import javax.swing.*;
import java.awt.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* Tabbed Popup Component for {@link TabFrame}.
*
* @author Jannis Weis
*/
public class TabbedPopup extends PanelPopup {
private JTabbedPane tabbedPane;
/**
* Creates a Popup that can hold multiple content panes using a TabbedPane.
*
* @param title the title of the popup.
*/
public TabbedPopup(final String title) {
this(title, null, null);
}
/**
* Creates a Popup that can hold multiple content panes using a TabbedPane.
*
* @param title the title of the popup.
* @param icon the icon of the popup.
* @param content the initial content pane.
*/
public TabbedPopup(final String title, final Icon icon, final Component content) {
super(title, icon, content);
setIcon(icon);
setTitle(title);
setContentPane(content);
setEnabled(false);
}
/**
* Creates a Popup that can hold multiple content panes using a TabbedPane.
*
* @param title the title of the popup.
* @param icon the icon of the popup.
*/
public TabbedPopup(final String title, final Icon icon) {
this(title, icon, null);
}
/**
* Creates a Popup that can hold multiple content panes using a TabbedPane.
*
* @param title the title of the popup.
* @param content the initial content pane.
*/
public TabbedPopup(final String title, final Component content) {
this(title, null, content);
}
/**
* Returns all currently installed content panes.
* i.e. this area all components currently added as tabs to
* the TabbedPane.
* {@see {@link #getTabbedPane()}}.
*
* @return a collection of components.
*/
public Collection<Component> getContentPanes() {
int size = getTabbedPane().getTabCount();
List<Component> compList = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
compList.add(tabbedPane.getComponentAt(i));
}
return compList;
}
/**
* Get the tabbed pane.
*
* @return the tabbed pane.
*/
public JTabbedPane getTabbedPane() {
if (tabbedPane == null) {
tabbedPane = createTabbedPane();
}
return tabbedPane;
}
/**
* Creates the tabbedPane.
* Overwrite this method to customize the tabbedPane used.
*
* @return a tabbed pane.
*/
protected JTabbedPane createTabbedPane() {
return new JTabbedPane();
}
@Override
public String getUIClassID() {
return "TabFrameTabbedPopupUI";
}
/**
* Gets the currently selected component from the TabbedPane.
* {@see {@link #getTabbedPane()}}.
*
* @return the selected component.
*/
public Component getContentPane() {
return tabbedPane.getSelectedComponent();
}
/**
* Adds the component to the tabbed pane if it isn't already added.
* This method exists to conform the interface methods.
* It is preferred to directly add to the TabbedPane obtained by
* {@link #getTabbedPane()}.
*
* @param component the component to add.
*/
public void setContentPane(final Component component) {
if (component == null) return;
if (getContentPanes().contains(component)) {
return;
}
getTabbedPane().addTab(component.getName(), component);
}
}

159
src/main/java/com/weis/darklaf/components/tabframe/ToggleSplitPane.java

@ -0,0 +1,159 @@
/*
* MIT License
*
* Copyright (c) 2019 Jannis Weis
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.weis.darklaf.components.tabframe;
import javax.swing.*;
import javax.swing.plaf.basic.BasicSplitPaneUI;
public class ToggleSplitPane extends JSplitPane {
private int disabledPos = 0;
private int disabledMax = -1;
private boolean resizable = true;
private boolean lastEnabled = true;
private double restorePercentage;
public ToggleSplitPane() {
this(null);
}
public ToggleSplitPane(final String name) {
setName(name);
putClientProperty("JSplitPane.style", "invisible");
}
public boolean isResizable() {
return resizable;
}
/**
* Set if the split pane should be able to resize.
*
* @param resizable true if it should resize.
*/
public void setResizable(final boolean resizable) {
this.resizable = resizable;
if (!resizable) {
lastEnabled = isEnabled();
setEnabled(false);
disabledPos = super.getDividerLocation();
disabledMax = getMaximumDividerLocation();
} else {
setEnabled(lastEnabled);
}
}
@Override
public void setEnabled(final boolean enabled) {
((BasicSplitPaneUI) getUI()).getDivider().setEnabled(enabled);
}
public void savePosition() {
savePosition(getRelativeDividerLocation());
}
public void savePosition(final double position) {
restorePercentage = Math.max(0, Math.min(1.0, position));
}
public double getRelativeDividerLocation() {
if (getOrientation() == HORIZONTAL_SPLIT) {
return getDividerLocation() / (double) getWidth();
} else {
return getDividerLocation() / (double) getHeight();
}
}
public void restorePosition() {
setDividerLocation(restorePercentage);
doLayout();
}
public void forceSetDividerLocation(final int location) {
super.setDividerLocation(location);
}
@Override
public int getDividerLocation() {
if (resizable) {
return super.getDividerLocation();
} else {
return disabledMax == disabledPos ? getMaximumDividerLocation() : disabledPos;
}
}
public void forceSetDividerLocation(final double proportionalLocation) {
if (proportionalLocation < 0.0 ||
proportionalLocation > 1.0) {
throw new IllegalArgumentException("proportional location must "
+ "be between 0.0 and 1.0.");
}
if (getOrientation() == VERTICAL_SPLIT) {
super.setDividerLocation((int) ((double) (getHeight()) * proportionalLocation));
} else {
super.setDividerLocation((int) ((double) (getWidth()) * proportionalLocation));
}
}
@Override
public int getLastDividerLocation() {
if (resizable) {
return super.getLastDividerLocation();
} else {
return disabledMax == disabledPos ? getMaximumDividerLocation() : disabledPos;
}
}
@Override
public void setDividerLocation(final int location) {
if (resizable) {
super.setDividerLocation(location);
} else if (disabledPos == disabledMax) {
super.setDividerLocation(getMaximumDividerLocation());
doLayout();
} else if (disabledPos == 0) {
super.setDividerLocation(0);
doLayout();
}
}
@Override
public int getMaximumDividerLocation() {
int max = getOrientation() == HORIZONTAL_SPLIT ? getWidth() : getHeight();
return Math.max(max, getMinimumDividerLocation());
}
@Override
public int getMinimumDividerLocation() {
var comp = getRightComponent();
return comp == null ? 0 : getOrientation() == HORIZONTAL_SPLIT
? comp.getMinimumSize().width
: comp.getMinimumSize().height;
}
}

91
src/main/java/com/weis/darklaf/decorators/HoverListener.java

@ -0,0 +1,91 @@
/*
* MIT License
*
* Copyright (c) 2019 Jannis Weis
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.weis.darklaf.decorators;
import org.jetbrains.annotations.Contract;
import javax.swing.*;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
/**
* @author Jannis Weis
*/
public class HoverListener implements MouseListener {
private final JComponent component;
private boolean hover = false;
private boolean scheduled = false;
@Contract(pure = true)
public HoverListener(final JComponent component) {
this.component = component;
}
public boolean isHover() {
return hover;
}
@Override
public void mouseClicked(final MouseEvent e) {
}
@Override
public void mousePressed(final MouseEvent e) {
}
@Override
public void mouseReleased(final MouseEvent e) {
}
@Override
public void mouseEntered(final MouseEvent e) {
if (!hover) {
hover = true;
scheduleRepaint();
}
}
private void scheduleRepaint() {
if (!scheduled) {
scheduled = true;
SwingUtilities.invokeLater(() -> {
component.invalidate();
component.repaint();
scheduled = false;
});
}
}
@Override
public void mouseExited(final MouseEvent e) {
if (hover) {
hover = false;
scheduleRepaint();
}
}
}

116
src/main/java/com/weis/darklaf/icons/RotatableIcon.java

@ -0,0 +1,116 @@
/*
* MIT License
*
* Copyright (c) 2019 Jannis Weis
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.weis.darklaf.icons;
import com.weis.darklaf.components.alignment.Alignment;
import org.jetbrains.annotations.Contract;
import javax.swing.*;
import java.awt.*;
import java.awt.geom.AffineTransform;
public class RotatableIcon implements Icon {
private Icon icon;
private Alignment alignment;
@Contract(pure = true)
public RotatableIcon() {
this(null);
}
@Contract(pure = true)
public RotatableIcon(final Icon icon) {
setIcon(icon);
this.alignment = null;
}
public void setIcon(final Icon icon) {
this.icon = icon == null ? EmptyIcon.create(0) : icon;
}
@Override
public void paintIcon(final Component c, final Graphics g, final int x, final int y) {
if (icon instanceof DarkSVGIcon) {
((DarkSVGIcon) icon).paintIcon(c, g, x, y, getAngle());
} else if (icon != null) {
Graphics2D g2 = (Graphics2D) g.create();
AffineTransform transform = new AffineTransform();
transform.rotate(getAngle(), x + getIconWidth() / 2.0, y + getIconHeight() / 2.0);
g2.transform(transform);
icon.paintIcon(c, g2, x, y);
}
}
@Contract(pure = true)
private double getAngle() {
double angle = 0.0;
switch (alignment) {
case NORTH:
case CENTER:
angle = 0.0;
break;
case SOUTH:
angle = 180.0;
break;
case EAST:
angle = 90.0;
break;
case WEST:
angle = 270.0;
break;
case NORTH_EAST:
angle = 45.0;
break;
case NORTH_WEST:
angle = 315.0;
break;
case SOUTH_EAST:
angle = 135.0;
break;
case SOUTH_WEST:
angle = 225.0;
break;
}
return Math.toRadians(angle);
}
@Override
public int getIconWidth() {
return icon.getIconWidth();
}
@Override
public int getIconHeight() {
return icon.getIconHeight();
}
public Alignment getOrientation() {
return alignment;
}
public void setOrientation(final Alignment alignment) {
this.alignment = alignment;
}
}

2
src/main/java/com/weis/darklaf/theme/Theme.java

@ -27,7 +27,7 @@ public abstract class Theme {
"borders", "button", "checkBox", "colorChooser", "comboBox", "fileChooser", "tristate", "borders", "button", "checkBox", "colorChooser", "comboBox", "fileChooser", "tristate",
"internalFrame", "label", "list", "menu", "menuBar", "menuItem", "optionPane", "panel", "popupMenu", "internalFrame", "label", "list", "menu", "menuBar", "menuItem", "optionPane", "panel", "popupMenu",
"progressBar", "radioButton", "rootPane", "scrollBar", "scrollPane", "separator", "slider", "spinner", "progressBar", "radioButton", "rootPane", "scrollBar", "scrollPane", "separator", "slider", "spinner",
"splitPane", "statusBar", "tabbedPane", "table", "text", "toggleButton", "toolBar", "toolTip", "tree", "splitPane", "statusBar", "tabbedPane", "tabFrame", "table", "text", "toggleButton", "toolBar", "toolTip", "tree",
}; };
public void beforeInstall() { public void beforeInstall() {

3
src/main/java/com/weis/darklaf/ui/button/DarkButtonUI.java

@ -123,8 +123,7 @@ public class DarkButtonUI extends BasicButtonUI {
if (isFullShadow(c)) { if (isFullShadow(c)) {
g.fillRect(0, 0, c.getWidth(), c.getHeight()); g.fillRect(0, 0, c.getWidth(), c.getHeight());
} else { } else {
g.fillRoundRect(borderSize, borderSize, c.getWidth() - 2 * borderSize, g.fillRoundRect(0, 0, c.getWidth(), c.getHeight(), arc, arc);
c.getHeight() - 2 * borderSize, arc, arc);
} }
} }
} else { } else {

223
src/main/java/com/weis/darklaf/ui/menu/DarkMenuItemUIBase.java

@ -63,6 +63,9 @@ public class DarkMenuItemUIBase extends BasicMenuItemUI {
return new DarkMenuItemUIBase(); return new DarkMenuItemUIBase();
} }
protected static void loadActionMap(@NotNull final LazyActionMap map) {
map.put(new Actions(Actions.CLICK));
}
@Override @Override
public void installUI(final JComponent c) { public void installUI(final JComponent c) {
@ -73,13 +76,6 @@ public class DarkMenuItemUIBase extends BasicMenuItemUI {
arrowIcon = null; arrowIcon = null;
} }
protected DarkMenuItemUIBase.Handler getHandler() {
if (handler == null) {
handler = new DarkMenuItemUIBase.Handler();
}
return handler;
}
protected void paintMenuItem(@NotNull final Graphics g, final JComponent c, protected void paintMenuItem(@NotNull final Graphics g, final JComponent c,
final Icon checkIcon, final Icon arrowIcon, final Icon checkIcon, final Icon arrowIcon,
final Color background, final Color foreground, final Color background, final Color foreground,
@ -264,6 +260,111 @@ public class DarkMenuItemUIBase extends BasicMenuItemUI {
* Code from BasicMenuItemUI. * Code from BasicMenuItemUI.
*/ */
protected DarkMenuItemUIBase.Handler getHandler() {
if (handler == null) {
handler = new DarkMenuItemUIBase.Handler();
}
return handler;
}
protected void updateAcceleratorBinding() {
KeyStroke accelerator = menuItem.getAccelerator();
InputMap windowInputMap = SwingUtilities.getUIInputMap(menuItem, JComponent.WHEN_IN_FOCUSED_WINDOW);
if (windowInputMap != null) {
windowInputMap.clear();
}
if (accelerator != null) {
if (windowInputMap == null) {
windowInputMap = createInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
SwingUtilities.replaceUIInputMap(menuItem, JComponent.WHEN_IN_FOCUSED_WINDOW, windowInputMap);
}
windowInputMap.put(accelerator, "doClick");
int modifiers = accelerator.getModifiers();
if (((modifiers & InputEvent.ALT_DOWN_MASK) != 0) &&
((modifiers & InputEvent.ALT_GRAPH_DOWN_MASK) != 0)) {
//When both ALT and ALT_GRAPH are set, add the ALT only
// modifier keystroke which is used for left ALT key.
// Unsetting the ALT_GRAPH will do that as ALT is already set
modifiers &= ~InputEvent.ALT_GRAPH_DOWN_MASK;
modifiers &= ~InputEvent.ALT_GRAPH_MASK;
KeyStroke keyStroke = KeyStroke.getKeyStroke(accelerator.getKeyCode(),
modifiers, accelerator.isOnKeyRelease());
windowInputMap.put(keyStroke, "doClick");
} else if (((modifiers & InputEvent.ALT_DOWN_MASK) != 0) && (
(modifiers & InputEvent.ALT_GRAPH_DOWN_MASK) == 0)) {
//When only ALT modifier is set, add the ALT + ALT_GRAPH
// modifier keystroke which is used for right ALT key
modifiers |= InputEvent.ALT_GRAPH_DOWN_MASK;
KeyStroke keyStroke = KeyStroke.getKeyStroke(accelerator.getKeyCode(),
modifiers, accelerator.isOnKeyRelease());
windowInputMap.put(keyStroke, "doClick");
} else if ((modifiers & InputEvent.ALT_GRAPH_DOWN_MASK) != 0) {
//When only ALT_GRAPH is set, remove the ALT_GRAPH only
// modifier and add the ALT and ALT+ALT_GRAPH modifiers
// keystroke which are used for left ALT key and right ALT
// respectively
modifiers &= ~InputEvent.ALT_GRAPH_DOWN_MASK;
modifiers &= ~InputEvent.ALT_GRAPH_MASK;
modifiers |= InputEvent.ALT_DOWN_MASK;
KeyStroke keyStroke = KeyStroke.getKeyStroke(accelerator.getKeyCode(),
modifiers, accelerator.isOnKeyRelease());
windowInputMap.put(keyStroke, "doClick");
//Add ALT+ALT_GRAPH modifier which is used for right ALT key
modifiers |= InputEvent.ALT_GRAPH_DOWN_MASK;
keyStroke = KeyStroke.getKeyStroke(accelerator.getKeyCode(),
modifiers, accelerator.isOnKeyRelease());
windowInputMap.put(keyStroke, "doClick");
}
}
}
protected InputMap createInputMap(final int condition) {
if (condition == JComponent.WHEN_IN_FOCUSED_WINDOW) {
return new ComponentInputMapUIResource(menuItem);
}
return null;
}
protected void updateCheckIcon() {
String prefix = getPropertyPrefix();
if (checkIcon == null || checkIcon instanceof UIResource) {
checkIcon = UIManager.getIcon(prefix + ".checkIcon");
//In case of column layout, .checkIconFactory is defined for this UI,
//the icon is compatible with it and useCheckAndArrow() is true,
//then the icon is handled by the checkIcon.
boolean isColumnLayout = MenuItemLayoutHelper.isColumnLayout(
menuItem.getComponentOrientation().isLeftToRight(), menuItem);
if (isColumnLayout) {
MenuItemCheckIconFactory iconFactory =
(MenuItemCheckIconFactory) UIManager.get(prefix + ".checkIconFactory");
if (iconFactory != null
&& MenuItemLayoutHelper.useCheckAndArrow(menuItem)
&& iconFactory.isCompatible(checkIcon, prefix)) {
checkIcon = iconFactory.getIcon(menuItem);
}
}
}
}
private static class Actions extends UIAction {
private static final String CLICK = "doClick";
Actions(final String key) {
super(key);
}
public void actionPerformed(final ActionEvent e) {
JMenuItem mi = (JMenuItem) e.getSource();
MenuSelectionManager.defaultManager().clearSelectedPath();
mi.doClick();
}
}
protected class Handler implements MenuDragMouseListener, MouseInputListener, PropertyChangeListener { protected class Handler implements MenuDragMouseListener, MouseInputListener, PropertyChangeListener {
// //
// MouseInputListener // MouseInputListener
@ -339,15 +440,15 @@ public class DarkMenuItemUIBase extends BasicMenuItemUI {
manager.setSelectedPath(path); manager.setSelectedPath(path);
} }
public void menuDragMouseExited(final MenuDragMouseEvent e) {
}
public void menuDragMouseDragged(@NotNull final MenuDragMouseEvent e) { public void menuDragMouseDragged(@NotNull final MenuDragMouseEvent e) {
MenuSelectionManager manager = e.getMenuSelectionManager(); MenuSelectionManager manager = e.getMenuSelectionManager();
MenuElement[] path = e.getPath(); MenuElement[] path = e.getPath();
manager.setSelectedPath(path); manager.setSelectedPath(path);
} }
public void menuDragMouseExited(final MenuDragMouseEvent e) {
}
public void menuDragMouseReleased(final MenuDragMouseEvent e) { public void menuDragMouseReleased(final MenuDragMouseEvent e) {
if (!menuItem.isEnabled()) { if (!menuItem.isEnabled()) {
return; return;
@ -388,106 +489,4 @@ public class DarkMenuItemUIBase extends BasicMenuItemUI {
} }
} }
} }
protected void updateAcceleratorBinding() {
KeyStroke accelerator = menuItem.getAccelerator();
InputMap windowInputMap = SwingUtilities.getUIInputMap(menuItem, JComponent.WHEN_IN_FOCUSED_WINDOW);
if (windowInputMap != null) {
windowInputMap.clear();
}
if (accelerator != null) {
if (windowInputMap == null) {
windowInputMap = createInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
SwingUtilities.replaceUIInputMap(menuItem, JComponent.WHEN_IN_FOCUSED_WINDOW, windowInputMap);
}
windowInputMap.put(accelerator, "doClick");
int modifiers = accelerator.getModifiers();
if (((modifiers & InputEvent.ALT_DOWN_MASK) != 0) &&
((modifiers & InputEvent.ALT_GRAPH_DOWN_MASK) != 0)) {
//When both ALT and ALT_GRAPH are set, add the ALT only
// modifier keystroke which is used for left ALT key.
// Unsetting the ALT_GRAPH will do that as ALT is already set
modifiers &= ~InputEvent.ALT_GRAPH_DOWN_MASK;
modifiers &= ~InputEvent.ALT_GRAPH_MASK;
KeyStroke keyStroke = KeyStroke.getKeyStroke(accelerator.getKeyCode(),
modifiers, accelerator.isOnKeyRelease());
windowInputMap.put(keyStroke, "doClick");
} else if (((modifiers & InputEvent.ALT_DOWN_MASK) != 0) && (
(modifiers & InputEvent.ALT_GRAPH_DOWN_MASK) == 0)) {
//When only ALT modifier is set, add the ALT + ALT_GRAPH
// modifier keystroke which is used for right ALT key
modifiers |= InputEvent.ALT_GRAPH_DOWN_MASK;
KeyStroke keyStroke = KeyStroke.getKeyStroke(accelerator.getKeyCode(),
modifiers, accelerator.isOnKeyRelease());
windowInputMap.put(keyStroke, "doClick");
} else if ((modifiers & InputEvent.ALT_GRAPH_DOWN_MASK) != 0) {
//When only ALT_GRAPH is set, remove the ALT_GRAPH only
// modifier and add the ALT and ALT+ALT_GRAPH modifiers
// keystroke which are used for left ALT key and right ALT
// respectively
modifiers &= ~InputEvent.ALT_GRAPH_DOWN_MASK;
modifiers &= ~InputEvent.ALT_GRAPH_MASK;
modifiers |= InputEvent.ALT_DOWN_MASK;
KeyStroke keyStroke = KeyStroke.getKeyStroke(accelerator.getKeyCode(),
modifiers, accelerator.isOnKeyRelease());
windowInputMap.put(keyStroke, "doClick");
//Add ALT+ALT_GRAPH modifier which is used for right ALT key
modifiers |= InputEvent.ALT_GRAPH_DOWN_MASK;
keyStroke = KeyStroke.getKeyStroke(accelerator.getKeyCode(),
modifiers, accelerator.isOnKeyRelease());
windowInputMap.put(keyStroke, "doClick");
}
}
}
protected InputMap createInputMap(final int condition) {
if (condition == JComponent.WHEN_IN_FOCUSED_WINDOW) {
return new ComponentInputMapUIResource(menuItem);
}
return null;
}
protected void updateCheckIcon() {
String prefix = getPropertyPrefix();
if (checkIcon == null || checkIcon instanceof UIResource) {
checkIcon = UIManager.getIcon(prefix + ".checkIcon");
//In case of column layout, .checkIconFactory is defined for this UI,
//the icon is compatible with it and useCheckAndArrow() is true,
//then the icon is handled by the checkIcon.
boolean isColumnLayout = MenuItemLayoutHelper.isColumnLayout(
menuItem.getComponentOrientation().isLeftToRight(), menuItem);
if (isColumnLayout) {
MenuItemCheckIconFactory iconFactory =
(MenuItemCheckIconFactory) UIManager.get(prefix + ".checkIconFactory");
if (iconFactory != null
&& MenuItemLayoutHelper.useCheckAndArrow(menuItem)
&& iconFactory.isCompatible(checkIcon, prefix)) {
checkIcon = iconFactory.getIcon(menuItem);
}
}
}
}
protected static void loadActionMap(@NotNull final LazyActionMap map) {
map.put(new Actions(Actions.CLICK));
}
private static class Actions extends UIAction {
private static final String CLICK = "doClick";
Actions(final String key) {
super(key);
}
public void actionPerformed(final ActionEvent e) {
JMenuItem mi = (JMenuItem) e.getSource();
MenuSelectionManager.defaultManager().clearSelectedPath();
mi.doClick();
}
}
} }

249
src/main/java/com/weis/darklaf/ui/menu/DarkMenuUI.java

@ -34,30 +34,25 @@ import java.util.Objects;
*/ */
public class DarkMenuUI extends DarkMenuItemUIBase { public class DarkMenuUI extends DarkMenuItemUIBase {
/* diagnostic aids -- should be false for production builds. */
private static final boolean TRACE = false; // trace creates and disposes
private static final boolean VERBOSE = false; // show reuse hits/misses
private static final boolean DEBUG = false; // show bad params, misc.
private static boolean crossMenuMnemonic = true;
/** /**
* The instance of {@code ChangeListener}. * The instance of {@code ChangeListener}.
*/ */
protected ChangeListener changeListener; protected ChangeListener changeListener;
/** /**
* The instance of {@code MenuListener}. * The instance of {@code MenuListener}.
*/ */
protected MenuListener menuListener; protected MenuListener menuListener;
private int lastMnemonic = 0; private int lastMnemonic = 0;
/** /**
* Uses as the parent of the windowInputMap when selected. * Uses as the parent of the windowInputMap when selected.
*/ */
private InputMap selectedWindowInputMap; private InputMap selectedWindowInputMap;
/* diagnostic aids -- should be false for production builds. */
private static final boolean TRACE = false; // trace creates and disposes
private static final boolean VERBOSE = false; // show reuse hits/misses
private static final boolean DEBUG = false; // show bad params, misc.
private static boolean crossMenuMnemonic = true;
/** /**
* Constructs a new instance of {@code BasicMenuUI}. * Constructs a new instance of {@code BasicMenuUI}.
* *
@ -75,6 +70,37 @@ public class DarkMenuUI extends DarkMenuItemUIBase {
map.put(new Actions(Actions.SELECT, null, true)); map.put(new Actions(Actions.SELECT, null, true));
} }
private static void appendPath(@NotNull final MenuElement[] path, final MenuElement elem) {
MenuElement[] newPath = new MenuElement[path.length + 1];
System.arraycopy(path, 0, newPath, 0, path.length);
newPath[path.length] = elem;
MenuSelectionManager.defaultManager().setSelectedPath(newPath);
}
@NotNull
protected static java.util.List<JPopupMenu> getPopups() {
MenuSelectionManager msm = MenuSelectionManager.defaultManager();
MenuElement[] p = msm.getSelectedPath();
java.util.List<JPopupMenu> list = new ArrayList<JPopupMenu>(p.length);
for (MenuElement element : p) {
if (element instanceof JPopupMenu) {
list.add((JPopupMenu) element);
}
}
return list;
}
protected static JPopupMenu getLastPopup() {
MenuSelectionManager msm = MenuSelectionManager.defaultManager();
MenuElement[] p = msm.getSelectedPath();
JPopupMenu popup = null;
for (int i = p.length - 1; popup == null && i >= 0; i--) {
if (p[i] instanceof JPopupMenu) { popup = (JPopupMenu) p[i]; }
}
return popup;
}
protected void installDefaults() { protected void installDefaults() {
super.installDefaults(); super.installDefaults();
@ -104,11 +130,6 @@ public class DarkMenuUI extends DarkMenuItemUIBase {
updateMnemonicBinding(); updateMnemonicBinding();
} }
void installLazyActionMap() {
LazyActionMap.installLazyActionMap(menuItem, BasicMenuUI.class,
getPropertyPrefix() + ".actionMap");
}
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
void updateMnemonicBinding() { void updateMnemonicBinding() {
int mnemonic = menuItem.getModel().getMnemonic(); int mnemonic = menuItem.getModel().getMnemonic();
@ -144,46 +165,6 @@ public class DarkMenuUI extends DarkMenuItemUIBase {
lastMnemonic = mnemonic; lastMnemonic = mnemonic;
} }
protected void uninstallKeyboardActions() {
super.uninstallKeyboardActions();
lastMnemonic = 0;
}
protected MouseInputListener createMouseInputListener(final JComponent c) {
return getHandler();
}
/**
* Returns an instance of {@code MenuListener}.
*
* @param c a component
* @return an instance of {@code MenuListener}
*/
protected MenuListener createMenuListener(final JComponent c) {
return null;
}
/**
* Returns an instance of {@code ChangeListener}.
*
* @param c a component
* @return an instance of {@code ChangeListener}
*/
protected ChangeListener createChangeListener(final JComponent c) {
return null;
}
protected PropertyChangeListener createPropertyChangeListener(final JComponent c) {
return getHandler();
}
protected DarkMenuItemUIBase.Handler getHandler() {
if (handler == null) {
handler = new DarkMenuUI.Handler();
}
return handler;
}
protected void uninstallDefaults() { protected void uninstallDefaults() {
menuItem.setArmed(false); menuItem.setArmed(false);
menuItem.setSelected(false); menuItem.setSelected(false);
@ -203,6 +184,22 @@ public class DarkMenuUI extends DarkMenuItemUIBase {
handler = null; handler = null;
} }
protected void uninstallKeyboardActions() {
super.uninstallKeyboardActions();
lastMnemonic = 0;
}
protected MouseInputListener createMouseInputListener(final JComponent c) {
return getHandler();
}
protected DarkMenuItemUIBase.Handler getHandler() {
if (handler == null) {
handler = new DarkMenuUI.Handler();
}
return handler;
}
protected MenuDragMouseListener createMenuDragMouseListener(final JComponent c) { protected MenuDragMouseListener createMenuDragMouseListener(final JComponent c) {
return getHandler(); return getHandler();
} }
@ -211,6 +208,10 @@ public class DarkMenuUI extends DarkMenuItemUIBase {
return (MenuKeyListener) getHandler(); return (MenuKeyListener) getHandler();
} }
protected PropertyChangeListener createPropertyChangeListener(final JComponent c) {
return getHandler();
}
public Dimension getMinimumSize(final JComponent c) { public Dimension getMinimumSize(final JComponent c) {
return (((JMenu) menuItem).isTopLevelMenu()) ? return (((JMenu) menuItem).isTopLevelMenu()) ?
c.getPreferredSize() : null; c.getPreferredSize() : null;
@ -224,6 +225,49 @@ public class DarkMenuUI extends DarkMenuItemUIBase {
return null; return null;
} }
/**
* Returns an instance of {@code ChangeListener}.
*
* @param c a component
* @return an instance of {@code ChangeListener}
*/
protected ChangeListener createChangeListener(final JComponent c) {
return null;
}
/**
* Returns an instance of {@code MenuListener}.
*
* @param c a component
* @return an instance of {@code MenuListener}
*/
protected MenuListener createMenuListener(final JComponent c) {
return null;
}
/*
* Set the background color depending on whether this is a toplevel menu
* in a menubar or a submenu of another menu.
*/
private void updateDefaultBackgroundColor() {
if (!UIManager.getBoolean("Menu.useMenuBarBackgroundForTopLevel")) {
return;
}
JMenu menu = (JMenu) menuItem;
if (menu.getBackground() instanceof UIResource) {
if (menu.isTopLevelMenu()) {
menu.setBackground(UIManager.getColor("MenuBar.background"));
} else {
menu.setBackground(UIManager.getColor(getPropertyPrefix() + ".background"));
}
}
}
void installLazyActionMap() {
LazyActionMap.installLazyActionMap(menuItem, BasicMenuUI.class,
getPropertyPrefix() + ".actionMap");
}
/** /**
* Sets timer to the {@code menu}. * Sets timer to the {@code menu}.
* *
@ -235,13 +279,6 @@ public class DarkMenuUI extends DarkMenuItemUIBase {
timer.start(); timer.start();
} }
private static void appendPath(@NotNull final MenuElement[] path, final MenuElement elem) {
MenuElement[] newPath = new MenuElement[path.length + 1];
System.arraycopy(path, 0, newPath, 0, path.length);
newPath[path.length] = elem;
MenuSelectionManager.defaultManager().setSelectedPath(newPath);
}
private static class Actions extends UIAction { private static class Actions extends UIAction {
private static final String SELECT = "selectMenu"; private static final String SELECT = "selectMenu";
@ -256,13 +293,6 @@ public class DarkMenuUI extends DarkMenuItemUIBase {
this.force = shouldForce; this.force = shouldForce;
} }
private JMenu getMenu(final ActionEvent e) {
if (e.getSource() instanceof JMenu) {
return (JMenu) e.getSource();
}
return menu;
}
public void actionPerformed(final ActionEvent e) { public void actionPerformed(final ActionEvent e) {
JMenu menu = getMenu(e); JMenu menu = getMenu(e);
if (!crossMenuMnemonic) { if (!crossMenuMnemonic) {
@ -302,6 +332,13 @@ public class DarkMenuUI extends DarkMenuItemUIBase {
} }
} }
private JMenu getMenu(final ActionEvent e) {
if (e.getSource() instanceof JMenu) {
return (JMenu) e.getSource();
}
return menu;
}
@Override @Override
public boolean accept(final Object c) { public boolean accept(final Object c) {
if (c instanceof JMenu) { if (c instanceof JMenu) {
@ -311,24 +348,6 @@ public class DarkMenuUI extends DarkMenuItemUIBase {
} }
} }
/*
* Set the background color depending on whether this is a toplevel menu
* in a menubar or a submenu of another menu.
*/
private void updateDefaultBackgroundColor() {
if (!UIManager.getBoolean("Menu.useMenuBarBackgroundForTopLevel")) {
return;
}
JMenu menu = (JMenu) menuItem;
if (menu.getBackground() instanceof UIResource) {
if (menu.isTopLevelMenu()) {
menu.setBackground(UIManager.getColor("MenuBar.background"));
} else {
menu.setBackground(UIManager.getColor(getPropertyPrefix() + ".background"));
}
}
}
/** /**
* Instantiated and used by a menu item to handle the current menu selection * Instantiated and used by a menu item to handle the current menu selection
* from mouse events. A MouseInputHandler processes and forwards all mouse events * from mouse events. A MouseInputHandler processes and forwards all mouse events
@ -446,21 +465,6 @@ public class DarkMenuUI extends DarkMenuItemUIBase {
} }
private class Handler extends DarkMenuItemUIBase.Handler implements MenuKeyListener { private class Handler extends DarkMenuItemUIBase.Handler implements MenuKeyListener {
//
// PropertyChangeListener
//
public void propertyChange(final PropertyChangeEvent e) {
if (Objects.equals(e.getPropertyName(), AbstractButton.
MNEMONIC_CHANGED_PROPERTY)) {
updateMnemonicBinding();
} else {
if (e.getPropertyName().equals("ancestor")) {
updateDefaultBackgroundColor();
}
super.propertyChange(e);
}
}
// //
// MouseInputListener // MouseInputListener
// //
@ -578,7 +582,6 @@ public class DarkMenuUI extends DarkMenuItemUIBase {
public void mouseMoved(final MouseEvent e) { public void mouseMoved(final MouseEvent e) {
} }
// //
// MenuDragHandler // MenuDragHandler
// //
@ -621,6 +624,21 @@ public class DarkMenuUI extends DarkMenuItemUIBase {
public void menuDragMouseReleased(final MenuDragMouseEvent e) { public void menuDragMouseReleased(final MenuDragMouseEvent e) {
} }
//
// PropertyChangeListener
//
public void propertyChange(final PropertyChangeEvent e) {
if (Objects.equals(e.getPropertyName(), AbstractButton.
MNEMONIC_CHANGED_PROPERTY)) {
updateMnemonicBinding();
} else {
if (e.getPropertyName().equals("ancestor")) {
updateDefaultBackgroundColor();
}
super.propertyChange(e);
}
}
// //
// MenuKeyListener // MenuKeyListener
// //
@ -667,29 +685,4 @@ public class DarkMenuUI extends DarkMenuItemUIBase {
public void menuKeyReleased(final MenuKeyEvent e) { public void menuKeyReleased(final MenuKeyEvent e) {
} }
} }
@NotNull
protected static java.util.List<JPopupMenu> getPopups() {
MenuSelectionManager msm = MenuSelectionManager.defaultManager();
MenuElement[] p = msm.getSelectedPath();
java.util.List<JPopupMenu> list = new ArrayList<JPopupMenu>(p.length);
for (MenuElement element : p) {
if (element instanceof JPopupMenu) {
list.add((JPopupMenu) element);
}
}
return list;
}
protected static JPopupMenu getLastPopup() {
MenuSelectionManager msm = MenuSelectionManager.defaultManager();
MenuElement[] p = msm.getSelectedPath();
JPopupMenu popup = null;
for (int i = p.length - 1; popup == null && i >= 0; i--) {
if (p[i] instanceof JPopupMenu) { popup = (JPopupMenu) p[i]; }
}
return popup;
}
} }

33
src/main/java/com/weis/darklaf/ui/splitpane/DarkSplitPaneBorder.java

@ -0,0 +1,33 @@
/*
* MIT License
*
* Copyright (c) 2019 Jannis Weis
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.weis.darklaf.ui.splitpane;
import javax.swing.plaf.BorderUIResource;
public class DarkSplitPaneBorder extends BorderUIResource.EmptyBorderUIResource {
public DarkSplitPaneBorder() {
super(0, 0, 0, 0);
}
}

30
src/main/java/com/weis/darklaf/ui/splitpane/DarkSplitPaneUI.java

@ -28,9 +28,7 @@ import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import javax.swing.*; import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.plaf.ComponentUI; import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicSplitPaneDivider; import javax.swing.plaf.basic.BasicSplitPaneDivider;
import javax.swing.plaf.basic.BasicSplitPaneUI; import javax.swing.plaf.basic.BasicSplitPaneUI;
import java.awt.*; import java.awt.*;
@ -44,7 +42,6 @@ public class DarkSplitPaneUI extends BasicSplitPaneUI implements PropertyChangeL
private static final int DIVIDER_DRAG_SIZE = 10; private static final int DIVIDER_DRAG_SIZE = 10;
private static final int DIVIDER_DRAG_OFFSET = 5; private static final int DIVIDER_DRAG_OFFSET = 5;
private int defaultDividerSize;
private Style style; private Style style;
protected DarkSplitPaneUI(final Style style) { protected DarkSplitPaneUI(final Style style) {
@ -60,13 +57,8 @@ public class DarkSplitPaneUI extends BasicSplitPaneUI implements PropertyChangeL
@Override @Override
public void installUI(final JComponent c) { public void installUI(final JComponent c) {
super.installUI(c); super.installUI(c);
defaultDividerSize = splitPane.getDividerSize();
if (style == Style.LINE) {
splitPane.setDividerSize(1);
} else if (style == Style.INVISIBLE) {
splitPane.setDividerSize(0);
}
splitPane.setContinuousLayout(true); splitPane.setContinuousLayout(true);
splitPane.setComponentZOrder(getDivider(), 0);
} }
@Override @Override
@ -81,6 +73,7 @@ public class DarkSplitPaneUI extends BasicSplitPaneUI implements PropertyChangeL
splitPane.setLayout(null); splitPane.setLayout(null);
} }
super.uninstallUI(c); super.uninstallUI(c);
divider = null;
} }
@Override @Override
@ -135,20 +128,7 @@ public class DarkSplitPaneUI extends BasicSplitPaneUI implements PropertyChangeL
} }
if (oldStyle != style) { if (oldStyle != style) {
if (style == Style.DEFAULT || oldStyle == Style.DEFAULT) { if (style == Style.DEFAULT || oldStyle == Style.DEFAULT) {
splitPane.setDividerSize(defaultDividerSize); splitPane.setUI(new DarkSplitPaneUI(style));
splitPane.remove(divider);
divider = createDefaultDivider();
Border b = divider.getBorder();
if (!(b instanceof UIResource)) {
divider.setBorder(UIManager.getBorder("SplitPaneDivider.border"));
}
Integer temp = (Integer) UIManager.get("SplitPane.dividerSize");
LookAndFeel.installProperty(splitPane, "dividerSize", temp == null ? 10 : temp);
divider.setDividerSize(splitPane.getDividerSize());
dividerSize = divider.getDividerSize();
splitPane.add(divider, JSplitPane.DIVIDER);
splitPane.invalidate();
} }
} }
} }
@ -188,9 +168,9 @@ public class DarkSplitPaneUI extends BasicSplitPaneUI implements PropertyChangeL
if (style == Style.LINE) { if (style == Style.LINE) {
g.setColor(getDividerLineColor()); g.setColor(getDividerLineColor());
if (orientation == JSplitPane.HORIZONTAL_SPLIT) { if (orientation == JSplitPane.HORIZONTAL_SPLIT) {
g.drawLine(DIVIDER_DRAG_OFFSET, 0, DIVIDER_DRAG_OFFSET, getHeight() - 1); g.drawLine(DIVIDER_DRAG_OFFSET, 0, DIVIDER_DRAG_OFFSET, getHeight());
} else { } else {
g.drawLine(0, DIVIDER_DRAG_OFFSET, getWidth() - 1, DIVIDER_DRAG_OFFSET); g.drawLine(0, DIVIDER_DRAG_OFFSET, getWidth(), DIVIDER_DRAG_OFFSET);
} }
} }
} }

12
src/main/java/com/weis/darklaf/ui/tabbedpane/DarkHandler.java

@ -88,7 +88,7 @@ public class DarkHandler extends TabbedPaneHandler {
ui.tabPane.remove(ui.leadingComp); ui.tabPane.remove(ui.leadingComp);
var val = e.getNewValue(); var val = e.getNewValue();
if (val instanceof Component) { if (val instanceof Component) {
ui.leadingComp = (Component) val; ui.leadingComp = ui.wrapClientComponent((Component) val);
ui.tabPane.add(ui.leadingComp); ui.tabPane.add(ui.leadingComp);
} else { } else {
ui.leadingComp = null; ui.leadingComp = null;
@ -97,7 +97,7 @@ public class DarkHandler extends TabbedPaneHandler {
ui.tabPane.remove(ui.trailingComp); ui.tabPane.remove(ui.trailingComp);
var val = e.getNewValue(); var val = e.getNewValue();
if (val instanceof Component) { if (val instanceof Component) {
ui.trailingComp = (Component) val; ui.trailingComp = ui.wrapClientComponent((Component) val);
ui.tabPane.add(ui.trailingComp); ui.tabPane.add(ui.trailingComp);
} else { } else {
ui.trailingComp = null; ui.trailingComp = null;
@ -112,7 +112,7 @@ public class DarkHandler extends TabbedPaneHandler {
ui.tabPane.remove(ui.northComp); ui.tabPane.remove(ui.northComp);
var val = e.getNewValue(); var val = e.getNewValue();
if (val instanceof Component) { if (val instanceof Component) {
ui.northComp = (Component) val; ui.northComp = ui.wrapClientComponent((Component) val);
ui.tabPane.add(ui.northComp); ui.tabPane.add(ui.northComp);
} else { } else {
ui.northComp = null; ui.northComp = null;
@ -121,7 +121,7 @@ public class DarkHandler extends TabbedPaneHandler {
ui.tabPane.remove(ui.southComp); ui.tabPane.remove(ui.southComp);
var val = e.getNewValue(); var val = e.getNewValue();
if (val instanceof Component) { if (val instanceof Component) {
ui.southComp = (Component) val; ui.southComp = ui.wrapClientComponent((Component) val);
ui.tabPane.add(ui.southComp); ui.tabPane.add(ui.southComp);
} else { } else {
ui.southComp = null; ui.southComp = null;
@ -130,7 +130,7 @@ public class DarkHandler extends TabbedPaneHandler {
ui.tabPane.remove(ui.eastComp); ui.tabPane.remove(ui.eastComp);
var val = e.getNewValue(); var val = e.getNewValue();
if (val instanceof Component) { if (val instanceof Component) {
ui.eastComp = (Component) val; ui.eastComp = ui.wrapClientComponent((Component) val);
ui.tabPane.add(ui.eastComp); ui.tabPane.add(ui.eastComp);
} else { } else {
ui.eastComp = null; ui.eastComp = null;
@ -139,7 +139,7 @@ public class DarkHandler extends TabbedPaneHandler {
ui.tabPane.remove(ui.westComp); ui.tabPane.remove(ui.westComp);
var val = e.getNewValue(); var val = e.getNewValue();
if (val instanceof Component) { if (val instanceof Component) {
ui.westComp = (Component) val; ui.westComp = ui.wrapClientComponent((Component) val);
ui.tabPane.add(ui.westComp); ui.tabPane.add(ui.westComp);
} else { } else {
ui.westComp = null; ui.westComp = null;

8
src/main/java/com/weis/darklaf/ui/tabbedpane/DarkScrollableTabSupport.java

@ -56,11 +56,11 @@ public class DarkScrollableTabSupport extends ScrollableTabSupport implements Mo
viewport.setView(tabPanel); viewport.setView(tabPanel);
viewport.addMouseWheelListener(this); viewport.addMouseWheelListener(this);
moreTabsButton = new MoreTabsButton(ui); moreTabsButton = ui.createMoreTabsButton();
moreTabsButton.setVisible(false); moreTabsButton.setVisible(false);
moreTabsButton.addActionListener(this); moreTabsButton.addActionListener(this);
newTabButton = new NewTabButton(ui); newTabButton = ui.createNewTabButton();
newTabButton.setVisible(Boolean.TRUE.equals(ui.tabPane.getClientProperty("JTabbedPane.showNewTabButton"))); newTabButton.setVisible(Boolean.TRUE.equals(ui.tabPane.getClientProperty("JTabbedPane.showNewTabButton")));
scrollPopupMenu = new ScrollPopupMenu(UIManager.getInt("TabbedPane.maxPopupHeight")); scrollPopupMenu = new ScrollPopupMenu(UIManager.getInt("TabbedPane.maxPopupHeight"));
@ -94,8 +94,8 @@ public class DarkScrollableTabSupport extends ScrollableTabSupport implements Mo
} }
@Override @Override
void createButtons() { void createButtons(final DarkTabbedPaneUIBridge ui) {
super.createButtons(); super.createButtons(ui);
ui.tabPane.remove(scrollForwardButton); ui.tabPane.remove(scrollForwardButton);
ui.tabPane.remove(scrollBackwardButton); ui.tabPane.remove(scrollBackwardButton);
} }

2
src/main/java/com/weis/darklaf/ui/tabbedpane/DarkTabAreaButton.java

@ -27,7 +27,7 @@ import javax.swing.*;
import javax.swing.plaf.UIResource; import javax.swing.plaf.UIResource;
import java.awt.*; import java.awt.*;
class DarkTabAreaButton extends JButton implements UIResource { public class DarkTabAreaButton extends JButton implements UIResource {
private DarkTabbedPaneUI ui; private DarkTabbedPaneUI ui;

7
src/main/java/com/weis/darklaf/ui/tabbedpane/DarkTabbedPaneScrollLayout.java

@ -72,15 +72,14 @@ public class DarkTabbedPaneScrollLayout extends TabbedPaneScrollLayout {
// Calculate how much space the tabs will need, based on the // Calculate how much space the tabs will need, based on the
// minimum size required to display largest child + content border // minimum size required to display largest child + content border
// //
if (tabPlacement == SwingConstants.LEFT || tabPlacement == SwingConstants.RIGHT) { if (!ui.isHorizontalTabPlacement()) {
int tabHeight = ui.calculateTabHeight(tabPlacement, ui.tabPane.getSelectedIndex(), int tabHeight = ui.calculateTabHeight(tabPlacement, ui.tabPane.getSelectedIndex(),
ui.getFontMetrics().getHeight()); ui.getFontMetrics().getHeight());
if (ui.scrollableTabSupport.moreTabsButton.isVisible()) { if (ui.scrollableTabSupport.moreTabsButton.isVisible()) {
tabHeight += ui.scrollableTabSupport.moreTabsButton.getPreferredSize().height; tabHeight += ui.scrollableTabSupport.moreTabsButton.getPreferredSize().height;
} }
height = Math.max(height, tabHeight); height = Math.max(height, tabHeight);
tabExtent = preferredTabAreaWidth(tabPlacement, tabExtent = preferredTabAreaWidth(tabPlacement, height - tabAreaInsets.top - tabAreaInsets.bottom);
height - tabAreaInsets.top - tabAreaInsets.bottom);
width += tabExtent; width += tabExtent;
} else { } else {
int tabWidth = ui.calculateTabWidth(tabPlacement, ui.tabPane.getSelectedIndex(), ui.getFontMetrics()); int tabWidth = ui.calculateTabWidth(tabPlacement, ui.tabPane.getSelectedIndex(), ui.getFontMetrics());
@ -300,7 +299,7 @@ public class DarkTabbedPaneScrollLayout extends TabbedPaneScrollLayout {
boolean verticalTabRuns = !ui.isHorizontalTabPlacement(); boolean verticalTabRuns = !ui.isHorizontalTabPlacement();
boolean leftToRight = ui.tabPane.getComponentOrientation().isLeftToRight(); boolean leftToRight = ui.tabPane.getComponentOrientation().isLeftToRight();
if (tabPlacement == SwingConstants.LEFT || tabPlacement == SwingConstants.RIGHT) { if (!ui.isHorizontalTabPlacement()) {
ui.maxTabWidth = ui.calculateMaxTabWidth(tabPlacement); ui.maxTabWidth = ui.calculateMaxTabWidth(tabPlacement);
} else { } else {
ui.maxTabHeight = ui.calculateMaxTabHeight(tabPlacement); ui.maxTabHeight = ui.calculateMaxTabHeight(tabPlacement);

85
src/main/java/com/weis/darklaf/ui/tabbedpane/DarkTabbedPaneUI.java

@ -1,5 +1,6 @@
package com.weis.darklaf.ui.tabbedpane; package com.weis.darklaf.ui.tabbedpane;
import com.weis.darklaf.components.UIResourceWrapper;
import com.weis.darklaf.util.DarkUIUtil; import com.weis.darklaf.util.DarkUIUtil;
import com.weis.darklaf.util.GraphicsContext; import com.weis.darklaf.util.GraphicsContext;
import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.Contract;
@ -7,6 +8,7 @@ import org.jetbrains.annotations.NotNull;
import javax.swing.*; import javax.swing.*;
import javax.swing.plaf.ComponentUI; import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource;
import java.awt.*; import java.awt.*;
import java.awt.event.FocusEvent; import java.awt.event.FocusEvent;
import java.awt.event.FocusListener; import java.awt.event.FocusListener;
@ -134,17 +136,44 @@ public class DarkTabbedPaneUI extends DarkTabbedPaneUIBridge {
} }
var lead = tabPane.getClientProperty("JTabbedPane.leadingComponent"); var lead = tabPane.getClientProperty("JTabbedPane.leadingComponent");
if (lead instanceof Component) { if (lead instanceof Component) {
leadingComp = (Component) lead; leadingComp = wrapClientComponent((Component) lead);
tabPane.add(leadingComp); tabPane.add(leadingComp);
} }
var trail = tabPane.getClientProperty("JTabbedPane.trailingComponent"); var trail = tabPane.getClientProperty("JTabbedPane.trailingComponent");
if (trail instanceof Component) { if (trail instanceof Component) {
trailingComp = (Component) trail; trailingComp = wrapClientComponent((Component) trail);
tabPane.add(trailingComp); tabPane.add(trailingComp);
} }
var north = tabPane.getClientProperty("JTabbedPane.northComponent");
if (north instanceof Component) {
northComp = wrapClientComponent((Component) north);
tabPane.add(northComp);
}
var south = tabPane.getClientProperty("JTabbedPane.southComponent");
if (south instanceof Component) {
southComp = wrapClientComponent((Component) south);
tabPane.add(southComp);
}
var west = tabPane.getClientProperty("JTabbedPane.westComponent");
if (west instanceof Component) {
westComp = wrapClientComponent((Component) west);
tabPane.add(westComp);
}
var east = tabPane.getClientProperty("JTabbedPane.eastComponent");
if (east instanceof Component) {
eastComp = wrapClientComponent((Component) east);
tabPane.add(eastComp);
}
dndEnabled = Boolean.TRUE.equals(tabPane.getClientProperty("JTabbedPane.dndEnabled")); dndEnabled = Boolean.TRUE.equals(tabPane.getClientProperty("JTabbedPane.dndEnabled"));
} }
protected Component wrapClientComponent(final Component component) {
if (component instanceof UIResource) {
return component;
}
return new UIResourceWrapper(component);
}
@Override @Override
protected void installListeners() { protected void installListeners() {
super.installListeners(); super.installListeners();
@ -269,27 +298,13 @@ public class DarkTabbedPaneUI extends DarkTabbedPaneUIBridge {
} }
} }
protected Color getTabBackgroundColor(final int tabIndex, final boolean isSelected, final boolean hover) {
if (isSelected) {
return hover ? UIManager.getColor("TabbedPane.selectHighlight")
: UIManager.getColor("TabbedPane.selected");
} else {
return hover ? UIManager.getColor("TabbedPane.highlight")
: tabPane.getBackgroundAt(tabIndex);
}
}
@Override
protected void paintCroppedTabEdge(final Graphics g) {
}
@Override @Override
protected void paintFocusIndicator(final Graphics g, final int tabPlacement, final Rectangle r, protected void paintFocusIndicator(final Graphics g, final int tabPlacement, final Rectangle r,
final int tabIndex, final Rectangle iconRect, final int tabIndex, final Rectangle iconRect,
final Rectangle textRect, final boolean isSelected) { final Rectangle textRect, final boolean isSelected) {
if (isSelected) { if (isSelected) {
if (!drawFocusBar()) return; if (!drawFocusBar()) return;
g.setColor(getAccentColor(DarkUIUtil.hasFocus(tabPane))); g.setColor(getAccentColor());
int focusSize = UIManager.getInt("TabbedPane.focusBarHeight"); int focusSize = UIManager.getInt("TabbedPane.focusBarHeight");
switch (tabPlacement) { switch (tabPlacement) {
case LEFT: case LEFT:
@ -308,6 +323,15 @@ public class DarkTabbedPaneUI extends DarkTabbedPaneUIBridge {
} }
} }
@Override
protected void paintCroppedTabEdge(final Graphics g) {
}
protected Color getAccentColor() {
boolean focus = DarkUIUtil.hasFocus(tabPane);
return getAccentColor(focus);
}
@Override @Override
protected void paintTabBorder(@NotNull final Graphics g, final int tabPlacement, final int tabIndex, protected void paintTabBorder(@NotNull final Graphics g, final int tabPlacement, final int tabIndex,
final int x, final int y, final int w, final int h, final int x, final int y, final int w, final int h,
@ -444,6 +468,11 @@ public class DarkTabbedPaneUI extends DarkTabbedPaneUIBridge {
return !Boolean.FALSE.equals(tabPane.getClientProperty("JTabbedPane.drawFocusBar")); return !Boolean.FALSE.equals(tabPane.getClientProperty("JTabbedPane.drawFocusBar"));
} }
@Override
protected int calculateTabHeight(final int tabPlacement, final int tabIndex, final int fontHeight) {
return super.calculateTabHeight(tabPlacement, tabIndex, fontHeight) - 1;
}
protected Color getAccentColor(final boolean focus) { protected Color getAccentColor(final boolean focus) {
return focus ? UIManager.getColor("TabbedPane.accentFocus") return focus ? UIManager.getColor("TabbedPane.accentFocus")
: UIManager.getColor("TabbedPane.accent"); : UIManager.getColor("TabbedPane.accent");
@ -498,8 +527,14 @@ public class DarkTabbedPaneUI extends DarkTabbedPaneUIBridge {
} }
} }
protected Icon getNewTabIcon() { protected Color getTabBackgroundColor(final int tabIndex, final boolean isSelected, final boolean hover) {
return UIManager.getIcon("TabbedPane.newTab.icon"); if (isSelected) {
return hover ? UIManager.getColor("TabbedPane.selectedHoverBackground")
: UIManager.getColor("TabbedPane.selectedBackground");
} else {
return hover ? UIManager.getColor("TabbedPane.hoverBackground")
: tabPane.getBackgroundAt(tabIndex);
}
} }
protected Icon getMoreTabsIcon() { protected Icon getMoreTabsIcon() {
@ -613,4 +648,16 @@ public class DarkTabbedPaneUI extends DarkTabbedPaneUIBridge {
} }
} }
public Icon getNewTabIcon() {
return UIManager.getIcon("TabbedPane.newTab.icon");
}
public JComponent createNewTabButton() {
return new NewTabButton(this);
}
public JButton createMoreTabsButton() {
return new MoreTabsButton(this);
}
} }

4
src/main/java/com/weis/darklaf/ui/tabbedpane/MoreTabsButton.java

@ -30,14 +30,14 @@ import org.jetbrains.annotations.NotNull;
import javax.swing.*; import javax.swing.*;
import java.awt.*; import java.awt.*;
class MoreTabsButton extends DarkTabAreaButton { public class MoreTabsButton extends DarkTabAreaButton {
protected final static String INFINITY = "\u221e"; protected final static String INFINITY = "\u221e";
protected final static int PAD = 2; protected final static int PAD = 2;
private DarkTabbedPaneUI ui; private DarkTabbedPaneUI ui;
protected final Icon icon; protected final Icon icon;
protected MoreTabsButton(final DarkTabbedPaneUI ui) { public MoreTabsButton(final DarkTabbedPaneUI ui) {
super(ui); super(ui);
this.ui = ui; this.ui = ui;
icon = ui.getMoreTabsIcon(); icon = ui.getMoreTabsIcon();

23
src/main/java/com/weis/darklaf/ui/tabbedpane/NewTabButton.java

@ -29,31 +29,36 @@ import javax.swing.*;
import javax.swing.plaf.UIResource; import javax.swing.plaf.UIResource;
import java.awt.*; import java.awt.*;
class NewTabButton extends JPanel implements UIResource { public class NewTabButton extends JPanel implements UIResource {
private DarkTabbedPaneUI ui; protected DarkTabbedPaneUI ui;
protected final JButton button; protected final JButton button;
protected NewTabButton(@NotNull final DarkTabbedPaneUI ui) { protected NewTabButton(@NotNull final DarkTabbedPaneUI ui) {
this.ui = ui; this.ui = ui;
button = new JButton(); button = createButton();
button.setIcon(ui.getNewTabIcon());
button.putClientProperty("JButton.variant", "shadow");
button.putClientProperty("JButton.buttonType", "square");
button.putClientProperty("JButton.thin", Boolean.TRUE);
button.setRolloverEnabled(true);
button.addActionListener(e -> { button.addActionListener(e -> {
var action = ui.getNewTabAction(); var action = ui.getNewTabAction();
if (action != null) { if (action != null) {
action.actionPerformed(e); action.actionPerformed(e);
} }
}); });
button.setOpaque(false);
add(button); add(button);
setOpaque(false); setOpaque(false);
setLayout(null); setLayout(null);
} }
protected JButton createButton() {
var button = new JButton();
button.setIcon(ui.getNewTabIcon());
button.putClientProperty("JButton.variant", "shadow");
button.putClientProperty("JButton.buttonType", "square");
button.putClientProperty("JButton.thin", Boolean.TRUE);
button.setRolloverEnabled(true);
button.setOpaque(false);
return button;
}
@Override @Override
public void doLayout() { public void doLayout() {
var b = button.getPreferredSize(); var b = button.getPreferredSize();

4
src/main/java/com/weis/darklaf/ui/tabbedpane/ScrollableTabSupport.java

@ -47,13 +47,13 @@ class ScrollableTabSupport implements ActionListener, ChangeListener {
viewport.setView(tabPanel); viewport.setView(tabPanel);
viewport.addChangeListener(this); viewport.addChangeListener(this);
croppedEdge = new CroppedEdge(ui); croppedEdge = new CroppedEdge(ui);
createButtons(); createButtons(ui);
} }
/** /**
* Recreates the scroll buttons and adds them to the TabbedPane. * Recreates the scroll buttons and adds them to the TabbedPane.
*/ */
void createButtons() { void createButtons(final DarkTabbedPaneUIBridge ui) {
if (scrollForwardButton != null) { if (scrollForwardButton != null) {
ui.tabPane.remove(scrollForwardButton); ui.tabPane.remove(scrollForwardButton);
scrollForwardButton.removeActionListener(this); scrollForwardButton.removeActionListener(this);

2
src/main/java/com/weis/darklaf/ui/tabbedpane/TabbedPaneHandler.java

@ -77,7 +77,7 @@ public class TabbedPaneHandler implements ChangeListener, ContainerListener, Foc
ui.calculatedBaseline = false; ui.calculatedBaseline = false;
} else if (Objects.equals(name, "tabPlacement")) { } else if (Objects.equals(name, "tabPlacement")) {
if (ui.scrollableTabLayoutEnabled()) { if (ui.scrollableTabLayoutEnabled()) {
ui.tabScroller.createButtons(); ui.tabScroller.createButtons(ui);
} }
ui.calculatedBaseline = false; ui.calculatedBaseline = false;
} else if (Objects.equals(name, "opaque") && isScrollLayout) { } else if (Objects.equals(name, "opaque") && isScrollLayout) {

382
src/main/java/com/weis/darklaf/ui/tabframe/DarkPanelPopupUI.java

@ -0,0 +1,382 @@
/*
* MIT License
*
* Copyright (c) 2019 Jannis Weis
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.weis.darklaf.ui.tabframe;
import com.weis.darklaf.components.JLabelUIResource;
import com.weis.darklaf.components.alignment.Alignment;
import com.weis.darklaf.components.border.MutableLineBorder;
import com.weis.darklaf.components.tabframe.PanelPopup;
import com.weis.darklaf.components.tabframe.TabFrame;
import com.weis.darklaf.components.tooltip.ToolTipContext;
import com.weis.darklaf.ui.panel.DarkPanelUI;
import com.weis.darklaf.util.DarkUIUtil;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource;
import java.awt.*;
import java.awt.event.AWTEventListener;
import java.awt.event.ActionEvent;
import java.awt.event.FocusEvent;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
public class DarkPanelPopupUI extends DarkPanelUI implements PropertyChangeListener, AWTEventListener {
protected HeaderButton closeButton;
private final Action closeAction = new AbstractAction() {
@Override
public void actionPerformed(final ActionEvent e) {
closeButton.doClick();
}
};
protected JPanel content;
protected JLabel label;
protected Color headerFocusBackground;
protected Color headerButtonFocusHoverBackground;
protected Color headerButtonFocusClickBackground;
protected Color headerBackground;
protected Color headerButtonHoverBackground;
protected Color headerButtonClickBackground;
protected String accelerator;
private PanelPopup popupComponent;
private JPanel header;
private MutableLineBorder headerBorder;
private MutableLineBorder contentBorder;
private boolean oldFocus;
@NotNull
@Contract("_ -> new")
public static ComponentUI createUI(final JComponent c) {
return new DarkPanelPopupUI();
}
@Override
public void installUI(final JComponent c) {
popupComponent = (PanelPopup) c;
super.installUI(c);
installDefaults();
installComponents();
installListeners();
}
protected void installDefaults() {
headerBackground = UIManager.getColor("TabFramePopup.headerBackground");
headerButtonHoverBackground = UIManager.getColor("TabFramePopup.headerButtonHoverBackground");
headerButtonClickBackground = UIManager.getColor("TabFramePopup.headerButtonClickBackground");
headerFocusBackground = UIManager.getColor("TabFramePopup.headerFocusBackground");
headerButtonFocusHoverBackground = UIManager.getColor("TabFramePopup.headerButtonFocusHoverBackground");
headerButtonFocusClickBackground = UIManager.getColor("TabFramePopup.headerButtonFocusClickBackground");
accelerator = UIManager.getString("TabFramePopup.closeAccelerator");
popupComponent.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT)
.put(KeyStroke.getKeyStroke(accelerator), accelerator);
popupComponent.getActionMap().put(accelerator, closeAction);
popupComponent.setLayout(new BorderLayout());
}
protected void installComponents() {
closeButton = createCloseButton();
label = createLabel();
header = createHeader();
JPanel headerContainer = new JPanel(new BorderLayout());
setHeaderBackground(false);
headerContainer.add(header, BorderLayout.CENTER);
headerContainer.setMinimumSize(UIManager.getDimension("TabFramePopup.minimumHeaderSize"));
content = new JPanel(new BorderLayout());
content.add(popupComponent.getContentPane(), BorderLayout.CENTER);
popupComponent.setLayout(new BorderLayout());
popupComponent.add(headerContainer, BorderLayout.NORTH);
popupComponent.add(content, BorderLayout.CENTER);
headerBorder = createBorder();
contentBorder = createBorder();
headerContainer.setBorder(headerBorder);
content.setBorder(contentBorder);
}
protected void installListeners() {
popupComponent.addPropertyChangeListener(this);
Toolkit.getDefaultToolkit().addAWTEventListener(this, AWTEvent.FOCUS_EVENT_MASK);
var frame = popupComponent.getTabFrame();
if (frame != null) {
frame.addPropertyChangeListener(this);
}
}
protected HeaderButton createCloseButton() {
var closeButton = new HeaderButton(UIManager.getIcon("TabFramePopup.close.icon"), this);
closeButton.setBorder(new EmptyBorder(4, 4, 4, 4));
closeButton.addActionListener(e -> popupComponent.close());
var tooltip = UIManager.getString("TabFramePopup.closeTooltipText");
closeButton.setToolTipText(tooltip);
return closeButton;
}
protected JLabel createLabel() {
var label = new JLabelUIResource(popupComponent.getTitle(), popupComponent.getIcon(), JLabel.LEFT);
label.setOpaque(false);
return label;
}
protected JPanel createHeader() {
var header = new JPanel();
header.setLayout(new BoxLayout(header, BoxLayout.X_AXIS));
header.add(Box.createHorizontalStrut(5));
header.add(label);
header.add(Box.createGlue());
header.add(closeButton);
header.add(Box.createHorizontalStrut(1));
header.setBorder(UIManager.getBorder("TabFramePopup.headerBorder"));
return header;
}
protected void setHeaderBackground(final boolean focus) {
closeButton.setFocus(focus);
if (header != null) {
header.setBackground(focus ? headerFocusBackground : headerBackground);
}
if (oldFocus != focus) {
if (header != null) {
header.repaint();
}
closeButton.repaint();
oldFocus = focus;
}
}
protected MutableLineBorder createBorder() {
var color = UIManager.getColor("TabFramePopup.borderColor");
return new MutableLineBorder.UIResource(0, 0, 0, 0, color);
}
@Override
public void uninstallUI(final JComponent c) {
super.uninstallUI(c);
uninstallComponents();
uninstallListeners();
popupComponent.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT)
.remove(KeyStroke.getKeyStroke(accelerator));
popupComponent.getActionMap().remove(accelerator);
popupComponent = null;
}
protected void uninstallComponents() {
popupComponent.removeAll();
}
protected void uninstallListeners() {
popupComponent.removePropertyChangeListener(this);
Toolkit.getDefaultToolkit().removeAWTEventListener(this);
var frame = popupComponent.getTabFrame();
if (frame != null) {
frame.removePropertyChangeListener(this);
}
}
public final Dimension getPreferredSize(@NotNull final JComponent c) {
if (!c.isEnabled()) {
return new Dimension(0, 0);
} else {
return super.getPreferredSize(c);
}
}
@Override
public Dimension getMinimumSize(@NotNull final JComponent c) {
if (!c.isEnabled()) {
return new Dimension(0, 0);
} else {
return super.getMinimumSize(c);
}
}
@Override
public Dimension getMaximumSize(@NotNull final JComponent c) {
if (!c.isEnabled()) {
return new Dimension(0, 0);
} else {
return super.getMaximumSize(c);
}
}
@Override
public void propertyChange(@NotNull final PropertyChangeEvent evt) {
var key = evt.getPropertyName();
if ("open".equals(key)) {
if (Boolean.TRUE.equals(evt.getNewValue())) {
setHeaderBackground(true);
}
} else if ("content".equals(key)) {
if (content == null) return;
content.add((Component) evt.getNewValue(), BorderLayout.CENTER);
content.invalidate();
} else if ("title".equals(key)) {
if (label == null) return;
label.setText(evt.getNewValue().toString());
label.repaint();
} else if ("icon".equals(key)) {
if (label == null) return;
label.setIcon((Icon) evt.getNewValue());
label.repaint();
} else if ("visibleTab".equals(key)) {
if (evt.getNewValue() instanceof TabFrame.TabFramePosition) {
if (((TabFrame.TabFramePosition) evt.getNewValue()).getAlignment() == popupComponent.getAlignment()) {
updateBorder(true);
}
}
} else if ("tabFrame".equals(key)) {
var oldVal = evt.getOldValue();
var newVal = evt.getNewValue();
if (oldVal instanceof TabFrame) {
((TabFrame) oldVal).removePropertyChangeListener(this);
}
if (newVal instanceof TabFrame) {
((TabFrame) newVal).addPropertyChangeListener(this);
}
} else if ("peerInsets".equals(key)) {
updateBorder(false);
}
}
protected void updateBorder(final boolean notifyPeer) {
if (popupComponent.getTabFrame() != null) {
var tabFrame = popupComponent.getTabFrame();
var status = tabFrame.getContentPane().getStatus();
var alignment = popupComponent.getAlignment();
var insets = getBorderSize(alignment, status);
applyBorderInsets(insets);
popupComponent.doLayout();
popupComponent.repaint();
if (notifyPeer) {
var peer = tabFrame.getPopupComponentAt(tabFrame.getPeer(popupComponent.getAlignment()));
peer.firePropertyChange("peerInsets", 0, 1);
}
}
}
@NotNull
protected Insets getBorderSize(@NotNull final Alignment a, final boolean[] info) {
var insets = new Insets(0, 0, 0, 0);
switch (a) {
case NORTH:
case NORTH_EAST:
if (info[Alignment.NORTH.getIndex()] || info[Alignment.NORTH_EAST.getIndex()]) {
insets.bottom = 1;
}
if (info[Alignment.NORTH.getIndex()] && a == Alignment.NORTH_EAST) {
insets.left = 1;
}
return insets;
case SOUTH:
case SOUTH_WEST:
if (info[Alignment.SOUTH_WEST.getIndex()] || info[Alignment.SOUTH.getIndex()]) {
insets.top = 1;
}
if (info[Alignment.SOUTH_WEST.getIndex()] && a == Alignment.SOUTH) {
insets.left = 1;
}
return insets;
case EAST:
case SOUTH_EAST:
if (info[Alignment.EAST.getIndex()] || info[Alignment.SOUTH_EAST.getIndex()]) {
insets.left = 1;
}
if (info[Alignment.EAST.getIndex()] && a == Alignment.SOUTH_EAST) {
insets.top = 1;
}
return insets;
case WEST:
case NORTH_WEST:
if (info[Alignment.NORTH_WEST.getIndex()] || info[Alignment.WEST.getIndex()]) {
insets.right = 1;
}
if (info[Alignment.NORTH_WEST.getIndex()] && a == Alignment.WEST) {
insets.top = 1;
}
return insets;
default:
return insets;
}
}
protected void applyBorderInsets(@NotNull final Insets insets) {
headerBorder.setInsets(insets.top, insets.left, 1, insets.right);
contentBorder.setInsets(0, insets.left, insets.bottom, insets.right);
}
protected boolean hasFocus() {
return oldFocus;
}
@Override
public void eventDispatched(@NotNull final AWTEvent event) {
if (event.getID() == FocusEvent.FOCUS_GAINED) {
boolean focus = DarkUIUtil.hasFocus(popupComponent);
setHeaderBackground(focus);
}
}
protected static final class HeaderButton extends JButton implements UIResource {
private final ToolTipContext context = new ToolTipContext(this);
private final DarkPanelPopupUI ui;
public HeaderButton(@NotNull final Icon icon, final DarkPanelPopupUI ui) {
super(icon);
this.ui = ui;
putClientProperty("JButton.buttonType", "square");
putClientProperty("JButton.forceRoundCorner", Boolean.TRUE);
putClientProperty("JButton.variant", "shadow");
setRolloverEnabled(true);
setFocus(false);
setOpaque(false);
}
public void setFocus(final boolean focus) {
putClientProperty("JButton.shadow.hover", focus ? ui.headerButtonFocusHoverBackground
: ui.headerButtonHoverBackground);
putClientProperty("JButton.shadow.click", focus ? ui.headerButtonFocusClickBackground
: ui.headerButtonClickBackground);
}
@Override
public Point getToolTipLocation(final MouseEvent event) {
return context.getToolTipLocation(event);
}
@Override
public JToolTip createToolTip() {
return context.getToolTip();
}
}
}

33
src/main/java/com/weis/darklaf/ui/tabframe/DarkTabFramePopupHeaderBorder.java

@ -0,0 +1,33 @@
/*
* MIT License
*
* Copyright (c) 2019 Jannis Weis
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.weis.darklaf.ui.tabframe;
import javax.swing.plaf.BorderUIResource;
public class DarkTabFramePopupHeaderBorder extends BorderUIResource.EmptyBorderUIResource {
public DarkTabFramePopupHeaderBorder() {
super(1, 0, 1, 0);
}
}

33
src/main/java/com/weis/darklaf/ui/tabframe/DarkTabFrameTabBorder.java

@ -0,0 +1,33 @@
/*
* MIT License
*
* Copyright (c) 2019 Jannis Weis
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.weis.darklaf.ui.tabframe;
import javax.swing.plaf.BorderUIResource;
public class DarkTabFrameTabBorder extends BorderUIResource.EmptyBorderUIResource {
public DarkTabFrameTabBorder() {
super(2, 5, 2, 5);
}
}

190
src/main/java/com/weis/darklaf/ui/tabframe/DarkTabFrameTabContainerUI.java

@ -0,0 +1,190 @@
/*
* MIT License
*
* Copyright (c) 2019 Jannis Weis
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.weis.darklaf.ui.tabframe;
import com.weis.darklaf.components.tabframe.TabFrame;
import com.weis.darklaf.components.tabframe.TabFrameTabContainer;
import com.weis.darklaf.decorators.HoverListener;
import com.weis.darklaf.ui.panel.DarkPanelUI;
import com.weis.darklaf.util.DarkUIUtil;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import javax.swing.plaf.ComponentUI;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
public class DarkTabFrameTabContainerUI extends DarkPanelUI implements PropertyChangeListener {
protected TabFrameTabContainer tabContainer;
private final MouseListener mouseListener = new MouseAdapter() {
@Override
public void mousePressed(final MouseEvent e) {
if (SwingUtilities.isLeftMouseButton(e)) {
tabContainer.getTabFrame().toggleTab(tabContainer.getOrientation(), tabContainer.getIndex(),
!tabContainer.isSelected());
}
}
};
private HoverListener hoverListener;
private Color selectedColor;
private Color hoverColor;
@NotNull
@Contract("_ -> new")
public static ComponentUI createUI(final JComponent c) {
return new DarkTabFrameTabContainerUI();
}
@Override
public void installUI(final JComponent c) {
tabContainer = (TabFrameTabContainer) c;
super.installUI(c);
installDefaults(tabContainer);
installListeners();
installAccelerator(tabContainer.getTabFrame());
}
protected void installListeners() {
hoverListener = new HoverListener(tabContainer);
tabContainer.addMouseListener(hoverListener);
tabContainer.addPropertyChangeListener(this);
tabContainer.addMouseListener(mouseListener);
var cont = tabContainer.getContent();
if (cont != null) {
cont.addMouseListener(hoverListener);
cont.addMouseListener(mouseListener);
}
}
protected void installAccelerator(final TabFrame tabFrame) {
if (tabFrame == null) return;
int acc = tabContainer.getAccelerator();
if (acc < 0) return;
tabFrame.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT)
.put(KeyStroke.getKeyStroke(UIManager.getString("TabFrame.acceleratorKeyCode") + " " + acc),
"accelerator_" + acc);
tabFrame.getActionMap().put("accelerator_" + acc, createAcceleratorAction(tabFrame));
}
protected Action createAcceleratorAction(final TabFrame tabFrame) {
return new AbstractAction() {
@Override
public void actionPerformed(final ActionEvent e) {
var a = tabContainer.getOrientation();
var index = tabContainer.getIndex();
if (!tabContainer.isSelected()) {
tabFrame.toggleTab(a, index, true);
} else {
var popup = tabFrame.getPopupComponentAt(a, index);
if (!DarkUIUtil.hasFocus(popup)) {
popup.requestFocus();
} else {
tabFrame.toggleTab(a, index, false);
}
}
}
};
}
@Override
public void uninstallUI(final JComponent c) {
super.uninstallUI(c);
tabContainer.removeMouseListener(hoverListener);
tabContainer.removeMouseListener(mouseListener);
tabContainer.removePropertyChangeListener(this);
var cont = tabContainer.getContent();
if (cont != null) {
cont.removeMouseListener(hoverListener);
cont.removeMouseListener(mouseListener);
}
hoverListener = null;
uninstallAccelerator(tabContainer.getTabFrame());
tabContainer = null;
}
protected void installDefaults(final JPanel p) {
super.installDefaults(p);
tabContainer.setOpaque(true);
selectedColor = UIManager.getColor("TabFrameTab.selectedBackground");
hoverColor = UIManager.getColor("TabFrameTab.hoverBackground");
}
protected void uninstallAccelerator(final TabFrame tabFrame) {
if (tabFrame == null) return;
int acc = tabContainer.getAccelerator();
String accAction = "accelerator_" + acc;
tabFrame.getActionMap().remove(accAction);
}
@Override
public void paint(@NotNull final Graphics g, @NotNull final JComponent c) {
g.setColor(getBackground(tabContainer));
g.fillRect(0, 0, c.getWidth(), c.getHeight());
super.paint(g, c);
}
public Color getBackground(@NotNull final TabFrameTabContainer tab) {
return tab.isSelected()
? selectedColor
: hoverListener.isHover() ? hoverColor : tab.getBackground();
}
@Override
public void propertyChange(@NotNull final PropertyChangeEvent evt) {
var key = evt.getPropertyName();
if ("content".equals(key)) {
var oldVal = evt.getOldValue();
var newVal = evt.getNewValue();
if (oldVal instanceof Component) {
((Component) oldVal).removeMouseListener(mouseListener);
((Component) oldVal).removeMouseListener(hoverListener);
}
if (newVal instanceof Component) {
((Component) newVal).addMouseListener(mouseListener);
((Component) newVal).addMouseListener(hoverListener);
}
} else if ("selected".equals(key)) {
if (tabContainer == null) return;
tabContainer.repaint();
} else if ("accelerator".equals(key)) {
if (tabContainer == null) return;
uninstallAccelerator(tabContainer.getTabFrame());
installAccelerator(tabContainer.getTabFrame());
} else if ("tabFrame".equals(key)) {
if (evt.getOldValue() instanceof TabFrame) {
uninstallAccelerator((TabFrame) evt.getOldValue());
}
if (evt.getNewValue() instanceof TabFrame) {
installAccelerator((TabFrame) evt.getNewValue());
}
}
}
}

274
src/main/java/com/weis/darklaf/ui/tabframe/DarkTabFrameTabLabelUI.java

@ -0,0 +1,274 @@
/*
* MIT License
*
* Copyright (c) 2019 Jannis Weis
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.weis.darklaf.ui.tabframe;
import com.weis.darklaf.components.alignment.Alignment;
import com.weis.darklaf.components.tabframe.TabFrame;
import com.weis.darklaf.components.tabframe.TabFrameTabLabel;
import com.weis.darklaf.decorators.HoverListener;
import com.weis.darklaf.icons.RotatableIcon;
import com.weis.darklaf.ui.label.DarkLabelUI;
import com.weis.darklaf.util.DarkUIUtil;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import sun.swing.SwingUtilities2;
import javax.swing.*;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicHTML;
import javax.swing.text.View;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
public class DarkTabFrameTabLabelUI extends DarkLabelUI implements PropertyChangeListener {
private TabFrameTabLabel tabComponent;
private final MouseListener mouseListener = new MouseAdapter() {
@Override
public void mousePressed(final MouseEvent e) {
if (SwingUtilities.isLeftMouseButton(e)) {
tabComponent.getTabFrame().toggleTab(tabComponent.getOrientation(), tabComponent.getIndex(),
!tabComponent.isSelected());
}
}
};
private HoverListener hoverListener;
private Color defaultFontColor;
private Color selectedFontColor;
private Color selectedColor;
private Color hoverColor;
private RotatableIcon rotatableIcon = new RotatableIcon();
private Rectangle paintIconR = new Rectangle();
private Rectangle paintTextR = new Rectangle();
@NotNull
@Contract("_ -> new")
public static ComponentUI createUI(final JComponent c) {
return new DarkTabFrameTabLabelUI();
}
@Override
public void paint(@NotNull final Graphics g, final JComponent c) {
g.setColor(getBackground(tabComponent));
g.fillRect(0, 0, tabComponent.getWidth(), tabComponent.getHeight());
JLabel label = (JLabel) c;
String text = label.getText();
Icon icon = getIcon();
if ((icon == null) && (text == null)) {
return;
}
FontMetrics fm = SwingUtilities2.getFontMetrics(label, g);
String clippedText = layout(label, fm, c.getWidth(), c.getHeight());
if (icon != null) {
icon.paintIcon(c, g, paintIconR.x, paintIconR.y);
}
if (text != null) {
View v = (View) c.getClientProperty(BasicHTML.propertyKey);
if (v != null) {
v.paint(g, paintTextR);
} else {
int textX = paintTextR.x;
int textY = paintTextR.y + fm.getAscent();
if (label.isEnabled()) {
paintEnabledText(label, g, clippedText, textX, textY);
} else {
paintDisabledText(label, g, clippedText, textX, textY);
}
}
}
}
@Override
public void installUI(final JComponent c) {
tabComponent = (TabFrameTabLabel) c;
super.installUI(c);
}
@Override
public void uninstallUI(final JComponent c) {
super.uninstallUI(c);
uninstallAccelerator(tabComponent.getTabFrame());
tabComponent.removeMouseListener(hoverListener);
tabComponent.removeMouseListener(mouseListener);
hoverListener = null;
tabComponent = null;
}
@Override
protected void installDefaults(final JLabel c) {
super.installDefaults(c);
tabComponent.setFont(UIManager.getFont("TabFrameTab.font"));
tabComponent.setOpaque(true);
defaultFontColor = UIManager.getColor("TabFrameTab.foreground");
selectedColor = UIManager.getColor("TabFrameTab.selectedBackground");
hoverColor = UIManager.getColor("TabFrameTab.hoverBackground");
selectedFontColor = UIManager.getColor("TabFrameTab.selectedForeground");
LookAndFeel.installBorder(c, "TabFrameTab.border");
}
@Override
protected void installListeners(final JLabel c) {
super.installListeners(c);
hoverListener = new HoverListener(tabComponent);
tabComponent.addMouseListener(hoverListener);
tabComponent.addMouseListener(mouseListener);
installAccelerator(tabComponent.getTabFrame());
}
protected void installAccelerator(final TabFrame tabFrame) {
if (tabFrame == null) return;
int acc = tabComponent.getAccelerator();
if (acc < 0) return;
System.out.println("install");
tabFrame.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT)
.put(KeyStroke.getKeyStroke(UIManager.getString("TabFrame.acceleratorKeyCode") + " " + acc),
"accelerator_" + acc);
tabFrame.getActionMap().put("accelerator_" + acc, createAcceleratorAction(tabFrame));
}
protected Action createAcceleratorAction(final TabFrame tabFrame) {
return new AbstractAction() {
@Override
public void actionPerformed(final ActionEvent e) {
var a = tabComponent.getOrientation();
var index = tabComponent.getIndex();
if (!tabComponent.isSelected()) {
tabFrame.toggleTab(a, index, true);
} else {
var popup = tabFrame.getPopupComponentAt(a, index);
if (!DarkUIUtil.hasFocus(popup)) {
popup.requestFocus();
} else {
tabFrame.toggleTab(a, index, false);
}
}
}
};
}
@Override
public void propertyChange(final PropertyChangeEvent e) {
super.propertyChange(e);
var key = e.getPropertyName();
if ("selected".equals(key)) {
tabComponent.setForeground(Boolean.TRUE.equals(e.getNewValue())
? selectedFontColor : defaultFontColor);
tabComponent.repaint();
} else if ("title".equals(key)) {
updateText();
} else if ("accelerator".equals(key)) {
updateText();
if (tabComponent == null) return;
uninstallAccelerator(tabComponent.getTabFrame());
installAccelerator(tabComponent.getTabFrame());
} else if ("orientation".equals(key)) {
rotatableIcon.setOrientation(mapOrientation(tabComponent.getOrientation()));
} else if ("tabFrame".equals(key)) {
if (e.getOldValue() instanceof TabFrame) {
uninstallAccelerator((TabFrame) e.getOldValue());
}
if (e.getNewValue() instanceof TabFrame) {
installAccelerator((TabFrame) e.getNewValue());
}
}
}
protected void updateText() {
var title = tabComponent.getTitle();
title = title == null ? "" : title;
int accelerator = tabComponent.getAccelerator();
if (accelerator >= 0 && accelerator <= 9) {
tabComponent.setText(accelerator + ":" + title);
tabComponent.setDisplayedMnemonicIndex(0);
} else {
tabComponent.setText(title);
tabComponent.setDisplayedMnemonicIndex(1);
}
}
protected void uninstallAccelerator(final TabFrame tabFrame) {
if (tabFrame == null) return;
int acc = tabComponent.getAccelerator();
String accAction = "accelerator_" + acc;
tabFrame.getActionMap().remove(accAction);
}
public Color getBackground(@NotNull final TabFrameTabLabel tab) {
return tab.isSelected()
? selectedColor
: hoverListener.isHover() ? hoverColor : tab.getBackground();
}
protected Icon getIcon() {
Icon icon = (tabComponent.isEnabled()) ? tabComponent.getIcon() : tabComponent.getDisabledIcon();
rotatableIcon.setIcon(icon);
if (rotatableIcon.getOrientation() == null) {
rotatableIcon.setOrientation(mapOrientation(tabComponent.getOrientation()));
}
return rotatableIcon;
}
private String layout(@NotNull final JLabel label, final FontMetrics fm,
final int width, final int height) {
Insets insets = label.getInsets(null);
String text = label.getText();
Rectangle paintViewR = new Rectangle();
paintViewR.x = insets.left;
paintViewR.y = insets.top;
paintViewR.width = width - (insets.left + insets.right);
paintViewR.height = height - (insets.top + insets.bottom);
paintIconR.x = paintIconR.y = paintIconR.width = paintIconR.height = 0;
paintTextR.x = paintTextR.y = paintTextR.width = paintTextR.height = 0;
return layoutCL(label, fm, text, getIcon(), paintViewR, paintIconR, paintTextR);
}
protected Alignment mapOrientation(@NotNull final Alignment newValue) {
switch (newValue) {
case CENTER:
case NORTH:
case NORTH_EAST:
case SOUTH:
case SOUTH_WEST:
return Alignment.NORTH;
case EAST:
case SOUTH_EAST:
return Alignment.WEST;
case WEST:
case NORTH_WEST:
return Alignment.EAST;
}
return Alignment.NORTH;
}
}

188
src/main/java/com/weis/darklaf/ui/tabframe/DarkTabFrameUI.java

@ -0,0 +1,188 @@
/*
* MIT License
*
* Copyright (c) 2019 Jannis Weis
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.weis.darklaf.ui.tabframe;
import com.weis.darklaf.components.border.MutableLineBorder;
import com.weis.darklaf.components.tabframe.PopupContainer;
import com.weis.darklaf.components.tabframe.TabFrame;
import com.weis.darklaf.components.tabframe.TabFramePopup;
import com.weis.darklaf.components.tabframe.TabFrameUI;
import com.weis.darklaf.util.DarkUIUtil;
import org.jdesktop.jxlayer.JXLayer;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.pbjar.jxlayer.plaf.ext.transform.DefaultTransformModel;
import org.pbjar.jxlayer.plaf.ext.transform.TransformUtils;
import javax.swing.*;
import javax.swing.plaf.ComponentUI;
import java.awt.*;
import java.awt.event.AWTEventListener;
import java.awt.event.MouseEvent;
/**
* UI class for {@link TabFrame}.
*
* @author Jannis Weis
* @since 2018
*/
public class DarkTabFrameUI extends TabFrameUI implements AWTEventListener {
private TabFrame tabFrame;
private JXLayer<JComponent> rotatePaneLeft;
private JXLayer<JComponent> rotatePaneRight;
private LayoutManager layout;
private MutableLineBorder topBorder;
private MutableLineBorder bottomBorder;
private MutableLineBorder leftBorder;
private MutableLineBorder rightBorder;
private Color lineColor;
@NotNull
@Contract("_ -> new")
public static ComponentUI createUI(final JComponent c) {
return new DarkTabFrameUI();
}
@Override
public void installUI(@NotNull final JComponent c) {
tabFrame = (TabFrame) c;
installDefaults();
installComponents();
installListeners();
}
protected void installDefaults() {
layout = createLayout();
tabFrame.setLayout(layout);
lineColor = UIManager.getColor("TabFrame.line");
topBorder = new MutableLineBorder.UIResource(0, 0, 1, 0, lineColor);
bottomBorder = new MutableLineBorder.UIResource(1, 0, 0, 0, lineColor);
rightBorder = new MutableLineBorder.UIResource(0, 0, 1, 0, lineColor);
leftBorder = new MutableLineBorder.UIResource(0, 0, 1, 0, lineColor);
tabFrame.getTopTabContainer().setBorder(topBorder);
tabFrame.getBottomTabContainer().setBorder(bottomBorder);
tabFrame.getRightTabContainer().setBorder(rightBorder);
tabFrame.getLeftTabContainer().setBorder(leftBorder);
}
private void installComponents() {
DefaultTransformModel rightTransformModel = new DefaultTransformModel();
rightTransformModel.setQuadrantRotation(1);
rightTransformModel.setScaleToPreferredSize(true);
rotatePaneRight = TransformUtils.createTransformJXLayer(tabFrame.getRightTabContainer(), rightTransformModel);
DefaultTransformModel leftTransformModel = new DefaultTransformModel();
leftTransformModel.setQuadrantRotation(3);
leftTransformModel.setScaleToPreferredSize(true);
rotatePaneLeft = TransformUtils.createTransformJXLayer(tabFrame.getLeftTabContainer(), leftTransformModel);
tabFrame.add(tabFrame.getTopTabContainer());
tabFrame.add(tabFrame.getBottomTabContainer());
tabFrame.add(rotatePaneLeft);
tabFrame.add(rotatePaneRight);
}
protected void installListeners() {
Toolkit.getDefaultToolkit().addAWTEventListener(this, AWTEvent.MOUSE_EVENT_MASK);
}
protected LayoutManager createLayout() {
return new TabFrameLayout(tabFrame, this);
}
@Override
public void uninstallUI(final JComponent c) {
tabFrame.remove(tabFrame.getTopTabContainer());
tabFrame.remove(tabFrame.getBottomTabContainer());
tabFrame.remove(rotatePaneLeft);
tabFrame.remove(rotatePaneRight);
layout = null;
lineColor = null;
topBorder = null;
bottomBorder = null;
leftBorder = null;
rightBorder = null;
rotatePaneLeft = null;
rotatePaneRight = null;
uninstallListeners();
}
protected void uninstallListeners() {
Toolkit.getDefaultToolkit().removeAWTEventListener(this);
}
public void updateBorders(final int topCount, final int bottomCount,
final int leftCount, final int rightCount) {
leftBorder.setRight(topCount > 0 ? 0 : 1);
leftBorder.setLeft(bottomCount > 0 ? 0 : 1);
rightBorder.setLeft(topCount > 0 ? 0 : 1);
rightBorder.setRight(bottomCount > 0 ? 0 : 1);
}
public int getTabSize(final TabFrame tabFrame) {
return UIManager.getInt("TabFrame.tabHeight");
}
public JComponent getLeftContainer() {
return rotatePaneLeft;
}
public JComponent getRightContainer() {
return rotatePaneRight;
}
public JComponent getTopContainer() {
return tabFrame.getTopTabContainer();
}
public JComponent getBottomContainer() {
return tabFrame.getBottomTabContainer();
}
@Override
public void eventDispatched(@NotNull final AWTEvent event) {
if (event.getID() == MouseEvent.MOUSE_PRESSED) {
var e = (MouseEvent) event;
var comp = e.getComponent().getComponentAt(e.getPoint());
var parent = DarkUIUtil.getParentOfType(TabFramePopup.class, comp);
if (parent != null) {
parent.getComponent().requestFocus();
return;
}
var parent2 = DarkUIUtil.getParentOfType(PopupContainer.class, comp);
if (parent2 != null) {
parent2.getPopup().requestFocus();
return;
}
var parent3 = DarkUIUtil.getParentOfType(TabFrame.class, comp);
if (parent3 != null && comp != null && !comp.hasFocus()) {
parent3.requestFocus();
}
}
}
}

190
src/main/java/com/weis/darklaf/ui/tabframe/DarkTabbedPopupUI.java

@ -0,0 +1,190 @@
/*
* MIT License
*
* Copyright (c) 2019 Jannis Weis
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.weis.darklaf.ui.tabframe;
import com.weis.darklaf.components.JPanelUIResource;
import com.weis.darklaf.components.border.MutableLineBorder;
import com.weis.darklaf.components.tabframe.TabbedPopup;
import com.weis.darklaf.ui.tabbedpane.DarkTabbedPaneUI;
import com.weis.darklaf.ui.tabbedpane.NewTabButton;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import javax.swing.plaf.ComponentUI;
import java.awt.*;
public class DarkTabbedPopupUI extends DarkPanelPopupUI {
protected Color headerHoverBackground;
protected Color headerSelectedBackground;
protected Color headerSelectedHoverBackground;
protected Color headerFocusHoverBackground;
protected Color headerFocusSelectedBackground;
protected Color headerFocusSelectedHoverBackground;
private TabbedPopup popupComponent;
private JTabbedPane tabbedPane;
private MutableLineBorder border;
private JPanel holder;
@NotNull
@Contract("_ -> new")
public static ComponentUI createUI(final JComponent c) {
return new DarkTabbedPopupUI();
}
@Override
public void installUI(final JComponent c) {
popupComponent = (TabbedPopup) c;
super.installUI(c);
}
@Override
protected void installDefaults() {
super.installDefaults();
headerHoverBackground = UIManager.getColor("TabFramePopup.headerHoverBackground");
headerSelectedBackground = UIManager.getColor("TabFramePopup.headerSelectedBackground");
headerSelectedHoverBackground = UIManager.getColor("TabFramePopup.headerSelectedHoverBackground");
headerFocusHoverBackground = UIManager.getColor("TabFramePopup.headerFocusHoverBackground");
headerFocusSelectedBackground = UIManager.getColor("TabFramePopup.headerFocusSelectedBackground");
headerFocusSelectedHoverBackground = UIManager.getColor("TabFramePopup.headerFocusSelectedHoverBackground");
}
@Override
protected void installComponents() {
closeButton = createCloseButton();
label = createLabel();
tabbedPane = createTabbedPane();
setupTabbedPane();
border = createBorder();
holder = new JPanel(new BorderLayout());
holder.add(tabbedPane, BorderLayout.CENTER);
holder.setBorder(border);
popupComponent.add(holder);
}
protected JTabbedPane createTabbedPane() {
var tabbedPane = popupComponent.getTabbedPane();
tabbedPane.setUI(new DarkTabFrameTabbedPaneUI());
tabbedPane.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT);
return tabbedPane;
}
protected void setupTabbedPane() {
label.setBorder(new EmptyBorder(0, 5, 0, 5));
var buttonHolder = new JPanelUIResource();
buttonHolder.setLayout(new BoxLayout(buttonHolder, BoxLayout.X_AXIS));
buttonHolder.add(Box.createHorizontalStrut(1));
buttonHolder.add(closeButton);
buttonHolder.add(Box.createHorizontalStrut(1));
buttonHolder.setOpaque(false);
tabbedPane.setOpaque(false);
tabbedPane.putClientProperty("JTabbedPane.leadingComponent", label);
tabbedPane.putClientProperty("JTabbedPane.trailingComponent", buttonHolder);
}
@Override
protected void uninstallComponents() {
super.uninstallComponents();
popupComponent.remove(tabbedPane);
}
@Override
protected void applyBorderInsets(@NotNull final Insets insets) {
border.setInsets(insets.top, insets.left, insets.bottom, insets.right);
}
@Override
protected void setHeaderBackground(final boolean focus) {
var oldFocus = hasFocus();
super.setHeaderBackground(focus);
if (oldFocus != focus) {
holder.setBackground(focus ? headerFocusBackground : headerBackground);
holder.repaint();
}
}
protected class DarkTabFrameTabbedPaneUI extends DarkTabbedPaneUI {
protected Color getTabBackgroundColor(final int tabIndex, final boolean isSelected, final boolean hover) {
if (hasFocus()) {
if (isSelected) {
return hover ? headerFocusSelectedHoverBackground
: headerFocusSelectedBackground;
} else {
return hover ? headerFocusHoverBackground
: headerFocusBackground;
}
} else {
if (isSelected) {
return hover ? headerSelectedHoverBackground
: headerSelectedBackground;
} else {
return hover ? headerHoverBackground
: headerBackground;
}
}
}
@Override
protected Color getTabBorderColor() {
return UIManager.getColor("TabFramePopup.borderColor");
}
@Override
protected Color getAccentColor(final boolean focus) {
return super.getAccentColor(focus || hasFocus());
}
@Override
public JComponent createNewTabButton() {
return new TabFrameNewTabButton(this);
}
@Override
public JButton createMoreTabsButton() {
return super.createMoreTabsButton();
}
}
protected class TabFrameNewTabButton extends NewTabButton {
protected TabFrameNewTabButton(@NotNull final DarkTabbedPaneUI ui) {
super(ui);
}
@Override
protected JButton createButton() {
var button = new HeaderButton(ui.getNewTabIcon(), DarkTabbedPopupUI.this);
button.putClientProperty("JButton.variant", "shadow");
button.putClientProperty("JButton.buttonType", "square");
button.putClientProperty("JButton.thin", Boolean.TRUE);
button.setRolloverEnabled(true);
button.setOpaque(false);
return button;
}
}
}

229
src/main/java/com/weis/darklaf/ui/tabframe/TabFrameLayout.java

@ -0,0 +1,229 @@
/*
* MIT License
*
* Copyright (c) 2019 Jannis Weis
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.weis.darklaf.ui.tabframe;
import com.weis.darklaf.components.alignment.Alignment;
import com.weis.darklaf.components.tabframe.TabFrame;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import java.awt.*;
/**
* @author Jannis Weis
* @since 2019
*/
public class TabFrameLayout implements LayoutManager {
private final TabFrame tabFrame;
private DarkTabFrameUI ui;
private int maxTabHeight;
private int maxTabWidth;
@Contract(pure = true)
public TabFrameLayout(@NotNull final TabFrame tabFrame, final DarkTabFrameUI ui) {
this.tabFrame = tabFrame;
this.ui = ui;
}
@Override
public void addLayoutComponent(final String name, final Component comp) {
}
@Override
public void removeLayoutComponent(final Component comp) {
}
@NotNull
@Override
public Dimension preferredLayoutSize(final Container parent) {
var b = tabFrame.getContentPane().getComponent().getPreferredSize();
return new Dimension(tabFrame.getLeftTabContainer().getWidth()
+ tabFrame.getRightTabContainer().getWidth() + b.width,
tabFrame.getTopTabContainer().getHeight()
+ tabFrame.getBottomTabContainer().getHeight() + b.height);
}
@NotNull
@Override
public Dimension minimumLayoutSize(final Container parent) {
var b = tabFrame.getContentPane().getComponent().getMinimumSize();
return new Dimension(tabFrame.getLeftTabContainer().getWidth()
+ tabFrame.getRightTabContainer().getWidth() + b.width,
tabFrame.getTopTabContainer().getHeight()
+ tabFrame.getBottomTabContainer().getHeight() + b.height);
}
@Override
public void layoutContainer(@NotNull final Container parent) {
var dim = parent.getSize();
int topSize = tabFrame.getTopTabCount();
int bottomSize = tabFrame.getBottomTabCount();
int leftSize = tabFrame.getLeftTabCount();
int rightSize = tabFrame.getRightTabCount();
layoutTopTab(dim, topSize, leftSize, rightSize);
layoutBottomTab(dim, bottomSize, leftSize, rightSize);
layoutLeftTab(dim, leftSize);
layoutRightTab(dim, rightSize);
ui.updateBorders(topSize, bottomSize, leftSize, rightSize);
var leftPane = ui.getLeftContainer();
var rightPane = ui.getRightContainer();
var topPane = ui.getTopContainer();
var bottomPane = ui.getBottomContainer();
tabFrame.getContentPane().getComponent().setBounds(leftPane.getWidth(), topPane.getHeight(),
dim.width - leftPane.getWidth() - rightPane.getWidth(),
dim.height - topPane.getHeight() - bottomPane.getHeight());
}
protected void layoutTopTab(final Dimension dim, final int topSize, final int leftSize, final int rightSize) {
if (topSize > 0) {
tabFrame.getTopTabContainer().setBounds(0, 0, dim.width, tabFrame.getTabSize());
layoutHorizontal(dim, Alignment.NORTH, Alignment.NORTH_EAST, 0, leftSize, rightSize);
} else {
tabFrame.getTopTabContainer().setBounds(0, 0, 0, 0);
}
}
protected void layoutBottomTab(final Dimension dim, final int bottomSize, final int leftSize, final int rightSize) {
if (bottomSize > 0) {
int size = tabFrame.getTabSize();
tabFrame.getBottomTabContainer().setBounds(0, dim.height - size, dim.width, size);
layoutHorizontal(dim, Alignment.SOUTH_WEST, Alignment.SOUTH, 1, leftSize, rightSize);
} else {
tabFrame.getBottomTabContainer().setBounds(0, 0, 0, 0);
}
}
protected void layoutHorizontal(final Dimension dim, final Alignment left, final Alignment right,
final int yOff, final int leftSize, final int rightSize) {
int tabHeight = calculateMaxTabSize(left);
var start = new Point(leftSize > 0 ? tabHeight : 0, yOff);
int leftEnd = layoutTabArea(start, left, true, tabHeight - 1);
start.x = rightSize > 0 ? dim.width - tabHeight : dim.width;
int rightStart = layoutTabArea(start, right, false, tabHeight - 1);
if (rightStart < leftEnd) {
shift(leftEnd - rightStart, right);
}
}
protected void layoutLeftTab(final Dimension dim, final int leftSize) {
var leftPane = ui.getLeftContainer();
var topPane = tabFrame.getTopTabContainer();
var bottomPane = tabFrame.getBottomTabContainer();
int tabWidth = calculateMaxTabSize(Alignment.WEST);
if (leftSize > 0) {
int height = dim.height - topPane.getHeight() - bottomPane.getHeight();
leftPane.setBounds(0, topPane.getHeight(), tabWidth, height + (height % 2));
tabFrame.getLeftTabContainer().setPreferredSize(new Dimension(leftPane.getHeight(),
leftPane.getWidth()));
tabFrame.getLeftTabContainer().setSize(tabFrame.getLeftTabContainer().getPreferredSize());
var start = new Point(leftPane.getHeight(), 0);
int topStart = layoutTabArea(start, Alignment.NORTH_WEST, false, tabWidth - 1);
start.x = 0;
int bottomEnd = layoutTabArea(start, Alignment.WEST, true, tabWidth - 1);
if (bottomEnd > topStart) {
shift(topStart - bottomEnd, Alignment.WEST);
}
} else {
tabFrame.getLeftTabContainer().setBounds(0, 0, 0, 0);
leftPane.setBounds(0, 0, 0, 0);
}
}
protected void layoutRightTab(final Dimension dim, final int rightSize) {
var rightPane = ui.getRightContainer();
var topPane = tabFrame.getTopTabContainer();
var bottomPane = tabFrame.getBottomTabContainer();
int tabWidth = calculateMaxTabSize(Alignment.EAST);
if (rightSize > 0) {
int height = dim.height - topPane.getHeight() - bottomPane.getHeight();
rightPane.setBounds(dim.width - tabWidth, topPane.getHeight(), tabWidth, height + (height % 2));
tabFrame.getRightTabContainer().setPreferredSize(new Dimension(rightPane.getHeight(), rightPane.getWidth()));
tabFrame.getRightTabContainer().setSize(tabFrame.getRightTabContainer().getPreferredSize());
var start = new Point(0, 0);
int topEnd = layoutTabArea(start, Alignment.EAST, true, tabWidth - 1);
start.x = tabFrame.getRightTabContainer().getWidth();
var bottomStart = layoutTabArea(start, Alignment.SOUTH_EAST, false, tabWidth - 1);
if (bottomStart < topEnd) {
shift(topEnd - bottomStart, Alignment.SOUTH_EAST);
}
} else {
tabFrame.getRightTabContainer().setBounds(0, 0, 0, 0);
topPane.setBounds(0, 0, 0, 0);
}
}
protected void shift(final int shift, final Alignment a) {
for (var c : tabFrame.tabsForAlignment(a)) {
var pos = c.getComponent().getLocation();
pos.x += shift;
c.getComponent().setLocation(pos);
}
}
protected int layoutTabArea(@NotNull final Point start, @NotNull final Alignment a,
final boolean forward, final int size) {
int x = start.x;
int y = start.y;
var bounds = new Rectangle(0, 0, 0, 0);
for (var c : tabFrame.tabsForAlignment(a)) {
bounds.width = getTabWidth(c.getComponent());
bounds.height = size;
if (forward) {
bounds.x = x;
bounds.y = y;
x += bounds.width;
} else {
x -= bounds.width;
bounds.x = x;
bounds.y = y;
}
c.getComponent().setBounds(bounds);
}
return x;
}
protected int getTabWidth(@NotNull final Component c) {
int maxWidth = tabFrame.getMaxTabWidth();
int width = c.getPreferredSize().width;
if (maxWidth < 0) {
return width;
} else {
return Math.min(maxWidth, width);
}
}
protected int calculateMaxTabSize(final Alignment a) {
int max = tabFrame.getTabSize();
for (var c : tabFrame.tabsForAlignment(a)) {
max = Math.max(max, c.getComponent().getMaximumSize().height + 1);
}
for (var c : tabFrame.tabsForAlignment(tabFrame.getPeer(a))) {
max = Math.max(max, c.getComponent().getMaximumSize().height + 1);
}
return max;
}
}

163
src/main/java/com/weis/darklaf/ui/table/DarkTableHeaderUIBridge.java

@ -115,17 +115,8 @@ public class DarkTableHeaderUIBridge extends BasicTableHeaderUI {
rolloverColumnUpdated(oldRolloverColumn, rolloverColumn); rolloverColumnUpdated(oldRolloverColumn, rolloverColumn);
} }
} }
} /**
* Creates the mouse listener for the {@code JTableHeader}.
*
* @return the mouse listener for the {@code JTableHeader}
*/
protected MouseInputListener createMouseInputListener() {
return new DarkTableHeaderUIBridge.MouseInputHandler();
} }
// Installation
// //
// Support for keyboard and mouse access // Support for keyboard and mouse access
// //
@ -138,28 +129,25 @@ public class DarkTableHeaderUIBridge extends BasicTableHeaderUI {
} }
} }
return newIndex; return newIndex;
} public void installUI(final JComponent c) {
super.installUI(c);
} }
/**
* Creates the mouse listener for the {@code JTableHeader}.
*
* @return the mouse listener for the {@code JTableHeader}
*/
protected MouseInputListener createMouseInputListener() {
return new DarkTableHeaderUIBridge.MouseInputHandler();
}
// Installation
protected int getSelectedColumnIndex() { protected int getSelectedColumnIndex() {
int numCols = header.getColumnModel().getColumnCount(); int numCols = header.getColumnModel().getColumnCount();
if (selectedColumnIndex >= numCols && numCols > 0) { if (selectedColumnIndex >= numCols && numCols > 0) {
selectedColumnIndex = numCols - 1; selectedColumnIndex = numCols - 1;
} }
return selectedColumnIndex; return selectedColumnIndex;
} /**
* Initializes JTableHeader properties such as font, foreground, and background.
* The font, foreground, and background properties are only set if their
* current value is either null or a UIResource, other properties are set
* if the current value is null.
*
* @see #installUI
*/
protected void installDefaults() {
LookAndFeel.installColorsAndFont(header, "TableHeader.background",
"TableHeader.foreground", "TableHeader.font");
LookAndFeel.installProperty(header, "opaque", Boolean.TRUE);
} }
/** /**
@ -168,18 +156,11 @@ public class DarkTableHeaderUIBridge extends BasicTableHeaderUI {
*/ */
void selectColumn(final int newColIndex) { void selectColumn(final int newColIndex) {
selectColumn(newColIndex, true); selectColumn(newColIndex, true);
} /**
* Attaches listeners to the JTableHeader.
*/
protected void installListeners() {
mouseInputListener = createMouseInputListener();
header.addMouseListener(mouseInputListener);
header.addMouseMotionListener(mouseInputListener);
header.addFocusListener(focusListener);
} }
// Uninstall methods public void installUI(final JComponent c) {
super.installUI(c);
}
void selectColumn(final int newColIndex, final boolean doScroll) { void selectColumn(final int newColIndex, final boolean doScroll) {
Rectangle repaintRect = header.getHeaderRect(selectedColumnIndex); Rectangle repaintRect = header.getHeaderRect(selectedColumnIndex);
@ -190,14 +171,6 @@ public class DarkTableHeaderUIBridge extends BasicTableHeaderUI {
if (doScroll) { if (doScroll) {
scrollToColumn(newColIndex); scrollToColumn(newColIndex);
} }
} public void uninstallUI(final JComponent c) {
uninstallDefaults();
uninstallListeners();
uninstallKeyboardActions();
header.remove(rendererPane);
rendererPane = null;
header = null;
} }
/** /**
@ -223,9 +196,17 @@ public class DarkTableHeaderUIBridge extends BasicTableHeaderUI {
vis.width = cellBounds.width; vis.width = cellBounds.width;
table.scrollRectToVisible(vis); table.scrollRectToVisible(vis);
} /** } /**
* Uninstalls default properties * Initializes JTableHeader properties such as font, foreground, and background.
* The font, foreground, and background properties are only set if their
* current value is either null or a UIResource, other properties are set
* if the current value is null.
*
* @see #installUI
*/ */
protected void uninstallDefaults() { protected void installDefaults() {
LookAndFeel.installColorsAndFont(header, "TableHeader.background",
"TableHeader.foreground", "TableHeader.font");
LookAndFeel.installProperty(header, "opaque", Boolean.TRUE);
} }
protected int selectPreviousColumn(final boolean doIt) { protected int selectPreviousColumn(final boolean doIt) {
@ -237,15 +218,6 @@ public class DarkTableHeaderUIBridge extends BasicTableHeaderUI {
} }
} }
return newIndex; return newIndex;
} /**
* Unregisters listeners.
*/
protected void uninstallListeners() {
header.removeMouseListener(mouseInputListener);
header.removeMouseMotionListener(mouseInputListener);
header.removeFocusListener(focusListener);
mouseInputListener = null;
} }
protected int changeColumnWidth(final TableColumn resizingColumn, protected int changeColumnWidth(final TableColumn resizingColumn,
@ -290,16 +262,17 @@ public class DarkTableHeaderUIBridge extends BasicTableHeaderUI {
} }
return 0; return 0;
} /** } /**
* Unregisters default key actions. * Attaches listeners to the JTableHeader.
*/ */
protected void uninstallKeyboardActions() { protected void installListeners() {
SwingUtilities.replaceUIInputMap(header, JComponent.WHEN_FOCUSED, null); mouseInputListener = createMouseInputListener();
SwingUtilities.replaceUIActionMap(header, null);
header.addMouseListener(mouseInputListener);
header.addMouseMotionListener(mouseInputListener);
header.addFocusListener(focusListener);
} }
// // Uninstall methods
// Support for mouse rollover
//
protected static class Actions extends UIAction { protected static class Actions extends UIAction {
public static final String TOGGLE_SORT_ORDER = public static final String TOGGLE_SORT_ORDER =
@ -446,17 +419,6 @@ public class DarkTableHeaderUIBridge extends BasicTableHeaderUI {
ui.changeColumnWidth(resizingColumn, th, oldWidth, newWidth); ui.changeColumnWidth(resizingColumn, th, oldWidth, newWidth);
} }
} /**
* Returns the index of the column header over which the mouse
* currently is. When the mouse is not over the table header,
* -1 is returned.
*
* @return the index of the current rollover column
* @see #rolloverColumnUpdated(int, int)
* @since 1.6
*/
protected int getRolloverColumn() {
return rolloverColumn;
} }
/** /**
@ -659,7 +621,66 @@ public class DarkTableHeaderUIBridge extends BasicTableHeaderUI {
header.getColumnModel().moveColumn(column, column); header.getColumnModel().moveColumn(column, column);
} }
} }
} /** }
public void uninstallUI(final JComponent c) {
uninstallDefaults();
uninstallListeners();
uninstallKeyboardActions();
header.remove(rendererPane);
rendererPane = null;
header = null;
}
/**
* Uninstalls default properties
*/
protected void uninstallDefaults() {
}
/**
* Unregisters listeners.
*/
protected void uninstallListeners() {
header.removeMouseListener(mouseInputListener);
header.removeMouseMotionListener(mouseInputListener);
header.removeFocusListener(focusListener);
mouseInputListener = null;
}
/**
* Unregisters default key actions.
*/
protected void uninstallKeyboardActions() {
SwingUtilities.replaceUIInputMap(header, JComponent.WHEN_FOCUSED, null);
SwingUtilities.replaceUIActionMap(header, null);
}
//
// Support for mouse rollover
//
/**
* Returns the index of the column header over which the mouse
* currently is. When the mouse is not over the table header,
* -1 is returned.
*
* @return the index of the current rollover column
* @see #rolloverColumnUpdated(int, int)
* @since 1.6
*/
protected int getRolloverColumn() {
return rolloverColumn;
}
/**
* This method gets called every time when a rollover column in the table * This method gets called every time when a rollover column in the table
* header is updated. Every look and feel that supports a rollover effect * header is updated. Every look and feel that supports a rollover effect
* in a table header should override this method and repaint the header. * in a table header should override this method and repaint the header.

100
src/main/java/com/weis/darklaf/ui/table/DarkTableUI.java

@ -249,6 +249,56 @@ public class DarkTableUI extends DarkTableUIBridge {
return dist; return dist;
} }
@Override
protected void paintCell(final Graphics g, final Rectangle cellRect, final int row, final int column) {
var bounds = table.getVisibleRect();
Point upperLeft = bounds.getLocation();
Point lowerRight = new Point(upperLeft.x + bounds.width - 1, upperLeft.y + bounds.height - 1);
int cMin = table.columnAtPoint(upperLeft);
int cMax = table.columnAtPoint(lowerRight);
boolean scrollLtR = !isScrollPaneRtl();
boolean ltr = table.getComponentOrientation().isLeftToRight();
int draggedIndex = viewIndexForColumn(table.getTableHeader().getDraggedColumn());
int dist = adjustDistance(table.getTableHeader().getDraggedDistance(),
table.getCellRect(row, draggedIndex, true),
table);
boolean isDragged = column == draggedIndex && dist != 0;
var r = new Rectangle(cellRect);
if (!scrollBarVisible()) {
if (ltr) {
if (column == cMax && !isDragged) r.width += 1;
} else {
if (column == cMin && !isDragged) r.width += 1;
}
} else if (!scrollLtR) {
if (ltr) {
if (column == cMax && !isDragged) r.width += 1;
if (column == cMin && !isDragged) {
r.width -= 1;
r.x += 1;
}
} else {
if (column == cMin && !isDragged) r.width += 1;
if (column == cMax && !isDragged) {
r.width -= 1;
r.x += 1;
}
}
}
if (table.isEditing() && table.getEditingRow() == row &&
table.getEditingColumn() == column) {
Component component = table.getEditorComponent();
component.setBounds(cellRect);
component.validate();
} else {
TableCellRenderer renderer = table.getCellRenderer(row, column);
Component component = table.prepareRenderer(renderer, row, column);
rendererPane.paintComponent(g, component, table, cellRect.x, cellRect.y,
cellRect.width, cellRect.height, true);
}
}
@Override @Override
protected void paintDraggedArea(@NotNull final Graphics g, final int rMin, final int rMax, protected void paintDraggedArea(@NotNull final Graphics g, final int rMin, final int rMax,
final int cMin, final int cMax, final int cMin, final int cMax,
@ -337,54 +387,4 @@ public class DarkTableUI extends DarkTableUIBridge {
g.fillRect(rect.x, y, rect.width, 1); g.fillRect(rect.x, y, rect.width, 1);
} }
} }
@Override
protected void paintCell(final Graphics g, final Rectangle cellRect, final int row, final int column) {
var bounds = table.getVisibleRect();
Point upperLeft = bounds.getLocation();
Point lowerRight = new Point(upperLeft.x + bounds.width - 1, upperLeft.y + bounds.height - 1);
int cMin = table.columnAtPoint(upperLeft);
int cMax = table.columnAtPoint(lowerRight);
boolean scrollLtR = !isScrollPaneRtl();
boolean ltr = table.getComponentOrientation().isLeftToRight();
int draggedIndex = viewIndexForColumn(table.getTableHeader().getDraggedColumn());
int dist = adjustDistance(table.getTableHeader().getDraggedDistance(),
table.getCellRect(row, draggedIndex, true),
table);
boolean isDragged = column == draggedIndex && dist != 0;
var r = new Rectangle(cellRect);
if (!scrollBarVisible()) {
if (ltr) {
if (column == cMax && !isDragged) r.width += 1;
} else {
if (column == cMin && !isDragged) r.width += 1;
}
} else if (!scrollLtR) {
if (ltr) {
if (column == cMax && !isDragged) r.width += 1;
if (column == cMin && !isDragged) {
r.width -= 1;
r.x += 1;
}
} else {
if (column == cMin && !isDragged) r.width += 1;
if (column == cMax && !isDragged) {
r.width -= 1;
r.x += 1;
}
}
}
if (table.isEditing() && table.getEditingRow() == row &&
table.getEditingColumn() == column) {
Component component = table.getEditorComponent();
component.setBounds(cellRect);
component.validate();
} else {
TableCellRenderer renderer = table.getCellRenderer(row, column);
Component component = table.prepareRenderer(renderer, row, column);
rendererPane.paintComponent(g, component, table, cellRect.x, cellRect.y,
cellRect.width, cellRect.height, true);
}
}
} }

77
src/main/java/com/weis/darklaf/ui/text/DarkCaret.java

@ -46,33 +46,17 @@ public class DarkCaret extends DefaultCaret implements UIResource {
public boolean getRoundedSelectionEdges() { public boolean getRoundedSelectionEdges() {
return ((DarkHighlightPainter) getSelectionPainter()).getRoundedEdges(); return ((DarkHighlightPainter) getSelectionPainter()).getRoundedEdges();
} @Override
protected void positionCaret(final MouseEvent e) {
super.positionCaret(e);
} }
public void setRoundedSelectionEdges(final boolean rounded) { public void setRoundedSelectionEdges(final boolean rounded) {
((DarkHighlightPainter) getSelectionPainter()).setRoundedEdges(rounded); ((DarkHighlightPainter) getSelectionPainter()).setRoundedEdges(rounded);
} private void adjustCaretLocation(@NotNull final MouseEvent e) { } @Override
if (e.isShiftDown() && getDot() != -1) { protected void positionCaret(final MouseEvent e) {
moveCaret(e); super.positionCaret(e);
} else {
positionCaret(e);
}
} }
public CaretStyle getStyle() { public CaretStyle getStyle() {
return style; return style;
} private void adjustFocus(final boolean inWindow) {
JTextComponent textArea = getComponent();
if ((textArea != null) && textArea.isEnabled() &&
textArea.isRequestFocusEnabled()) {
if (inWindow) {
textArea.requestFocusInWindow();
} else {
textArea.requestFocus();
}
}
} }
public void setStyle(final CaretStyle style) { public void setStyle(final CaretStyle style) {
@ -84,22 +68,18 @@ public class DarkCaret extends DefaultCaret implements UIResource {
this.style = s; this.style = s;
repaint(); repaint();
} }
} @Override }
protected synchronized void damage(final Rectangle r) {
if (r != null) { private void adjustCaretLocation(@NotNull final MouseEvent e) {
validateWidth(r); // Check for "0" or "1" caret width if (e.isShiftDown() && getDot() != -1) {
x = r.x - 1; moveCaret(e);
y = r.y; } else {
width = r.width + 4; positionCaret(e);
height = r.height;
repaint();
} }
} }
public boolean isAlwaysVisible() { public boolean isAlwaysVisible() {
return alwaysVisible; return alwaysVisible;
} public boolean getPasteOnMiddleMouseClick() {
return pasteOnMiddleMouseClick;
} }
/** /**
@ -122,15 +102,46 @@ public class DarkCaret extends DefaultCaret implements UIResource {
} }
} }
private void adjustFocus(final boolean inWindow) {
JTextComponent textArea = getComponent();
if ((textArea != null) && textArea.isEnabled() &&
textArea.isRequestFocusEnabled()) {
if (inWindow) {
textArea.requestFocusInWindow();
} else {
textArea.requestFocus();
}
}
}
@Override
protected synchronized void damage(final Rectangle r) {
if (r != null) {
validateWidth(r); // Check for "0" or "1" caret width
x = r.x - 1;
y = r.y;
width = r.width + 4;
height = r.height;
repaint();
}
}
@Override
protected Highlighter.HighlightPainter getSelectionPainter() {
return selectionPainter;
}
public boolean getPasteOnMiddleMouseClick() {
return pasteOnMiddleMouseClick;
}
public enum CaretStyle { public enum CaretStyle {
VERTICAL_LINE_STYLE, VERTICAL_LINE_STYLE,
UNDERLINE_STYLE, UNDERLINE_STYLE,
BLOCK_STYLE, BLOCK_STYLE,
BLOCK_BORDER_STYLE, BLOCK_BORDER_STYLE,
THICK_VERTICAL_LINE_STYLE THICK_VERTICAL_LINE_STYLE
} @Override
protected Highlighter.HighlightPainter getSelectionPainter() {
return selectionPainter;
} }

16
src/main/java/com/weis/darklaf/ui/text/DarkTextFieldUI.java

@ -44,6 +44,14 @@ public class DarkTextFieldUI extends DarkTextFieldUIBridge implements PropertyCh
getComponent().repaint(); getComponent().repaint();
} }
}; };
private long lastSearchEvent;
private final PopupMenuListener searchPopupListener = new PopupMenuAdapter() {
@Override
public void popupMenuWillBecomeInvisible(@NotNull final PopupMenuEvent e) {
lastSearchEvent = System.currentTimeMillis();
}
};
private boolean clearHovered;
private final MouseMotionListener mouseMotionListener = (MouseMovementListener) e -> updateCursor(e.getPoint()); private final MouseMotionListener mouseMotionListener = (MouseMovementListener) e -> updateCursor(e.getPoint());
private final KeyListener keyListener = new KeyAdapter() { private final KeyListener keyListener = new KeyAdapter() {
@Override @Override
@ -55,7 +63,6 @@ public class DarkTextFieldUI extends DarkTextFieldUIBridge implements PropertyCh
}); });
} }
}; };
private long lastSearchEvent;
private final MouseListener mouseListener = (MouseClickListener) e -> { private final MouseListener mouseListener = (MouseClickListener) e -> {
ClickAction actionUnder = getActionUnder(e.getPoint()); ClickAction actionUnder = getActionUnder(e.getPoint());
if (actionUnder == ClickAction.CLEAR) { if (actionUnder == ClickAction.CLEAR) {
@ -64,13 +71,6 @@ public class DarkTextFieldUI extends DarkTextFieldUIBridge implements PropertyCh
showSearchPopup(); showSearchPopup();
} }
}; };
private final PopupMenuListener searchPopupListener = new PopupMenuAdapter() {
@Override
public void popupMenuWillBecomeInvisible(@NotNull final PopupMenuEvent e) {
lastSearchEvent = System.currentTimeMillis();
}
};
private boolean clearHovered;
@NotNull @NotNull
@Contract("_ -> new") @Contract("_ -> new")

10
src/main/java/com/weis/darklaf/ui/text/DarkTextPaneUI.java

@ -23,11 +23,6 @@ public class DarkTextPaneUI extends DarkEditorPaneUI {
* Implementation of BasicEditorPaneUI * Implementation of BasicEditorPaneUI
*/ */
@Override
protected String getPropertyPrefix() {
return "TextPane";
}
@Override @Override
public void installUI(final JComponent c) { public void installUI(final JComponent c) {
super.installUI(c); super.installUI(c);
@ -37,4 +32,9 @@ public class DarkTextPaneUI extends DarkEditorPaneUI {
protected void propertyChange(final PropertyChangeEvent evt) { protected void propertyChange(final PropertyChangeEvent evt) {
super.propertyChange(evt); super.propertyChange(evt);
} }
@Override
protected String getPropertyPrefix() {
return "TextPane";
}
} }

4
src/main/java/com/weis/darklaf/util/DarkUIUtil.java

@ -209,10 +209,6 @@ public final class DarkUIUtil {
return SwingUtilities.getWindowAncestor(owner) == w; return SwingUtilities.getWindowAncestor(owner) == w;
} }
public static Color getTreeTextBackground() {
return UIManager.getColor("Tree.textBackground");
}
public static int getFocusAcceleratorKeyMask() { public static int getFocusAcceleratorKeyMask() {
Toolkit tk = Toolkit.getDefaultToolkit(); Toolkit tk = Toolkit.getDefaultToolkit();
if (tk instanceof SunToolkit) { if (tk instanceof SunToolkit) {

489
src/main/java/org/pbjar/jxlayer/plaf/ext/MouseEventUI.java

@ -0,0 +1,489 @@
/*
Copyright (c) 2009, Piet Blok
All rights reserved.
<p>
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
<p>
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* Neither the name of the copyright holder nor the names of the
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
<p>
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.pbjar.jxlayer.plaf.ext;
import org.jdesktop.jxlayer.JXLayer;
import org.jdesktop.jxlayer.plaf.AbstractLayerUI;
import org.jdesktop.jxlayer.plaf.LayerUI;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.plaf.ComponentUI;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException;
/**
* This class provides for {@link MouseEvent} re-dispatching. It may be used to set a tool tip on
* {@link JXLayer}'s glass pane and still have the child components receive {@link MouseEvent}s.
* <p>
* <b>Note:</b> A {@link MouseEventUI} instance cannot be shared and can be set
* to a single {@link JXLayer} instance only.
* <p/>
*/
public class MouseEventUI<V extends JComponent> extends AbstractLayerUI<V> {
static {
/*
* The instantiating of a JInternalFrame before a MouseEventUI is set to
* a JXLayer containing a JDesktopPane that has no JInternalFrames set,
* prevents the failing of re dispatched MouseEvents.
*
* This is a work around a problem that I don't really understand.
* Please see
* http://forums.java.net/jive/thread.jspa?threadID=66763&tstart=0 for a
* discussion on this problem.
*/
new JInternalFrame();
}
@Nullable
private Component lastEnteredTarget, lastPressedTarget;
private boolean dispatchingMode = false;
@Nullable
private JXLayer<? extends V> installedLayer;
/**
* Overridden to override the {@link LayerUI} implementation that only consults the view.
* <p>
* This implementation is a copy of the {@link ComponentUI#contains(JComponent, int, int)}
* method.
*/
@SuppressWarnings("deprecation")
@Override
public boolean contains(@NotNull final JComponent c, final int x, final int y) {
return c.inside(x, y);
}
/**
* Overridden to check if this {@link LayerUI} has not been installed already, and to set the
* argument {@code component} as the installed {@link JXLayer}.
*
* @throws IllegalStateException when this {@link LayerUI} has been installed already
* @see #getInstalledLayer()
*/
@SuppressWarnings("unchecked")
@Override
public void installUI(@NotNull final JComponent component) throws IllegalStateException {
super.installUI(component);
if (installedLayer != null) {
throw new IllegalStateException(this.getClass().getName()
+ " cannot be shared between multiple layers");
}
installedLayer = (JXLayer<? extends V>) component;
}
/**
* Overridden to remove the installed {@link JXLayer}.
*/
@Override
public void uninstallUI(@NotNull final JComponent c) {
installedLayer = null;
super.uninstallUI(c);
}
/**
* Overridden to only get the following event types: {@link AWTEvent#MOUSE_EVENT_MASK}, {@link
* AWTEvent#MOUSE_MOTION_EVENT_MASK} and {@link AWTEvent#MOUSE_WHEEL_EVENT_MASK}.
*/
@Override
public long getLayerEventMask() {
return AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK
| AWTEvent.MOUSE_WHEEL_EVENT_MASK;
}
/**
* Overridden to allow for re-dispatching of mouse events to their intended (visual) recipients,
* rather than to the components according to their bounds.
*/
@Override
public void eventDispatched(final AWTEvent event, @NotNull final JXLayer<? extends V> layer) {
if (event instanceof MouseEvent) {
MouseEvent mouseEvent = (MouseEvent) event;
if (!dispatchingMode) {
// Process an original mouse event
dispatchingMode = true;
try {
redispatch(mouseEvent, layer);
} finally {
dispatchingMode = false;
}
} else {
// Process a generated mouse event
/*
* Added a check, because on mouse entered or exited, the cursor
* may be set to specific dragging cursors.
*/
if (MouseEvent.MOUSE_ENTERED == mouseEvent.getID()
|| MouseEvent.MOUSE_EXITED == mouseEvent.getID()) {
layer.getGlassPane().setCursor(null);
} else {
Component component = mouseEvent.getComponent();
layer.getGlassPane().setCursor(component.getCursor());
}
}
}
}
/**
* Re-dispatches the event to the first component in the hierarchy that has a {@link
* MouseWheelListener} registered.
*/
@Override
protected void processMouseWheelEvent(@NotNull final MouseWheelEvent event,
@NotNull final JXLayer<? extends V> jxlayer) {
/*
* Only process an event if it is not already consumed. This may be the
* case if this LayerUI is contained in a wrapped hierarchy.
*/
if (!event.isConsumed()) {
/*
* Since we will create a new event, the argument event must be
* consumed.
*/
event.consume();
/*
* Find a target up in the hierarchy that has
* MouseWheelEventListeners registered.
*/
Component target = event.getComponent();
Component newTarget = findWheelListenerComponent(target);
if (newTarget == null) {
newTarget = jxlayer.getParent();
}
/*
* Convert the location relative to the new target
*/
Point point = SwingUtilities.convertPoint(event.getComponent(),
event.getPoint(), newTarget);
/*
* Create a new event and dispatch it.
*/
newTarget.dispatchEvent(createMouseWheelEvent(event, point,
newTarget));
}
}
@NotNull
private Point calculateTargetPoint(@NotNull final JXLayer<? extends V> layer,
@NotNull final MouseEvent mouseEvent) {
Point point = mouseEvent.getPoint();
SwingUtilities.convertPointToScreen(point, mouseEvent.getComponent());
SwingUtilities.convertPointFromScreen(point, layer);
/*
* Removed the contains check because it results in jumping when
* dragging internal frames in a desktop pane and dragging outside the
* boundaries of the desktop.
*
* Introduced this check to solve some scrolling problem, but don't
* quite remember the specifics. Maybe that problem is gone by other
* changes.
*/
// Rectangle layerBounds = layer.getBounds();
// Container parent = layer.getParent();
// Rectangle parentRectangle = new Rectangle(-layerBounds.x,
// -layerBounds.y, parent.getWidth(), parent.getHeight());
// if (parentRectangle.contains(point)) {
return transformPoint(layer, point);
// } else {
// return new Point(-1, -1);
// }
}
@NotNull
private MouseWheelEvent createMouseWheelEvent(
@NotNull final MouseWheelEvent mouseWheelEvent, @NotNull final Point point, @NotNull final Component target) {
return new MouseWheelEvent(target, //
mouseWheelEvent.getID(), //
mouseWheelEvent.getWhen(), //
mouseWheelEvent.getModifiersEx(), //
point.x, //
point.y, //
mouseWheelEvent.getClickCount(), //
mouseWheelEvent.isPopupTrigger(), //
mouseWheelEvent.getScrollType(), //
mouseWheelEvent.getScrollAmount(), //
mouseWheelEvent.getWheelRotation() //
);
}
private void dispatchMouseEvent(@Nullable final MouseEvent mouseEvent) {
if (mouseEvent != null) {
Component target = mouseEvent.getComponent();
target.dispatchEvent(mouseEvent);
/*
* Used to check the re dispatching behavior
*/
// switch (mouseEvent.getID()) {
// case (MouseEvent.MOUSE_PRESSED):
// System.out.println();
// case (MouseEvent.MOUSE_RELEASED):
// case (MouseEvent.MOUSE_CLICKED):
// System.out.println("Dispatched mouse event " + mouseEvent);
// }
}
}
@Contract("null -> null")
@Nullable
private Component findWheelListenerComponent(@Nullable final Component target) {
if (target == null) {
return null;
} else if (target.getMouseWheelListeners().length == 0) {
return findWheelListenerComponent(target.getParent());
} else {
return target;
}
}
private void generateEnterExitEvents(@NotNull final JXLayer<? extends V> layer,
@NotNull final MouseEvent originalEvent, final Component newTarget, @NotNull final Point realPoint) {
if (lastEnteredTarget != newTarget) {
dispatchMouseEvent(transformMouseEvent(layer, originalEvent,
lastEnteredTarget, realPoint, MouseEvent.MOUSE_EXITED));
lastEnteredTarget = newTarget;
dispatchMouseEvent(transformMouseEvent(layer, originalEvent,
lastEnteredTarget, realPoint, MouseEvent.MOUSE_ENTERED));
}
}
@SuppressWarnings("DuplicatedCode")
@Nullable
private Component getListeningComponent(@NotNull final MouseEvent event, @NotNull final Component component) {
Component comp;
switch (event.getID()) {
case MouseEvent.MOUSE_CLICKED:
case MouseEvent.MOUSE_ENTERED:
case MouseEvent.MOUSE_EXITED:
case MouseEvent.MOUSE_PRESSED:
case MouseEvent.MOUSE_RELEASED:
comp = getMouseListeningComponent(component);
break;
case MouseEvent.MOUSE_DRAGGED:
case MouseEvent.MOUSE_MOVED:
comp = getMouseMotionListeningComponent(component);
break;
case MouseEvent.MOUSE_WHEEL:
comp = getMouseWheelListeningComponent(component);
break;
default:
comp = null;
}
return comp;
}
@Nullable
private Component getMouseListeningComponent(@NotNull final Component component) {
if (component.getMouseListeners().length > 0) {
return component;
} else {
Container parent = component.getParent();
if (parent != null) {
return getMouseListeningComponent(parent);
} else {
return null;
}
}
}
@SuppressWarnings("Duplicates")
@Nullable
private Component getMouseMotionListeningComponent(@NotNull final Component component) {
/*
* Mouse motion events may result in MOUSE_ENTERED and MOUSE_EXITED.
*
* Therefore, components with MouseListeners registered should be
* returned as well.
*/
if (component.getMouseMotionListeners().length > 0
|| component.getMouseListeners().length > 0) {
return component;
} else {
Container parent = component.getParent();
if (parent != null) {
return getMouseMotionListeningComponent(parent);
} else {
return null;
}
}
}
@Nullable
private Component getMouseWheelListeningComponent(@NotNull final Component component) {
if (component.getMouseWheelListeners().length > 0) {
return component;
} else {
Container parent = component.getParent();
if (parent != null) {
return getMouseWheelListeningComponent(parent);
} else {
return null;
}
}
}
@Nullable
private Component getTarget(@NotNull final JXLayer<? extends V> layer, @NotNull final Point targetPoint) {
Component view = layer.getView();
if (view == null) {
return null;
} else {
Point viewPoint = SwingUtilities.convertPoint(layer, targetPoint,
view);
return SwingUtilities.getDeepestComponentAt(view, viewPoint.x,
viewPoint.y);
}
}
@SuppressWarnings("Duplicates")
private void redispatch(@NotNull final MouseEvent originalEvent,
@NotNull final JXLayer<? extends V> layer) {
if (layer.getView() != null) {
if (originalEvent.getComponent() != layer.getGlassPane()) {
originalEvent.consume();
}
MouseEvent newEvent = null;
Point realPoint = calculateTargetPoint(layer, originalEvent);
Component realTarget = getTarget(layer, realPoint);
if (realTarget != null) {
realTarget = getListeningComponent(originalEvent, realTarget);
}
switch (originalEvent.getID()) {
case MouseEvent.MOUSE_PRESSED:
newEvent = transformMouseEvent(layer, originalEvent, realTarget, realPoint);
if (newEvent != null) {
lastPressedTarget = newEvent.getComponent();
}
break;
case MouseEvent.MOUSE_RELEASED:
newEvent =
transformMouseEvent(layer, originalEvent, lastPressedTarget, realPoint);
lastPressedTarget = null;
break;
case MouseEvent.MOUSE_ENTERED:
case MouseEvent.MOUSE_EXITED:
generateEnterExitEvents(layer, originalEvent, realTarget, realPoint);
break;
case MouseEvent.MOUSE_MOVED:
newEvent = transformMouseEvent(layer, originalEvent, realTarget, realPoint);
generateEnterExitEvents(layer, originalEvent, realTarget, realPoint);
break;
case MouseEvent.MOUSE_DRAGGED:
newEvent =
transformMouseEvent(layer, originalEvent, lastPressedTarget, realPoint);
generateEnterExitEvents(layer, originalEvent, realTarget, realPoint);
break;
case MouseEvent.MOUSE_CLICKED:
newEvent = transformMouseEvent(layer, originalEvent, realTarget, realPoint);
break;
case (MouseEvent.MOUSE_WHEEL):
redispatchMouseWheelEvent((MouseWheelEvent) originalEvent, realTarget, layer);
break;
}
dispatchMouseEvent(newEvent);
}
}
private void redispatchMouseWheelEvent(@NotNull final MouseWheelEvent mouseWheelEvent,
final Component target, @NotNull final JXLayer<? extends V> layer) {
MouseWheelEvent newEvent = this.transformMouseWheelEvent(
mouseWheelEvent, target, layer);
processMouseWheelEvent(newEvent, layer);
}
@Nullable
private MouseEvent transformMouseEvent(@NotNull final JXLayer<? extends V> layer,
@NotNull final MouseEvent mouseEvent, final Component target, @NotNull final Point realPoint) {
return transformMouseEvent(layer, mouseEvent, target, realPoint,
mouseEvent.getID());
}
@Nullable
private MouseEvent transformMouseEvent(@NotNull final JXLayer<? extends V> layer,
@NotNull final MouseEvent mouseEvent, @Nullable final Component target, @NotNull final Point targetPoint, final int id) {
if (target == null) {
return null;
} else {
Point newPoint = new Point(targetPoint);
SwingUtilities.convertPointToScreen(newPoint, layer);
SwingUtilities.convertPointFromScreen(newPoint, target);
return new MouseEvent(target, //
id, //
mouseEvent.getWhen(), //
mouseEvent.getModifiersEx(), //
newPoint.x, //
newPoint.y, //
mouseEvent.getClickCount(), //
mouseEvent.isPopupTrigger(), //
mouseEvent.getButton());
}
}
@NotNull
private MouseWheelEvent transformMouseWheelEvent(
@NotNull final MouseWheelEvent mouseWheelEvent, final Component t,
final JXLayer<? extends V> layer) {
var target = t;
if (target == null) {
target = layer;
}
Point point = SwingUtilities.convertPoint(mouseWheelEvent.getComponent(),
mouseWheelEvent.getPoint(), target);
return createMouseWheelEvent(mouseWheelEvent,
point, target);
}
@NotNull
private Point transformPoint(final JXLayer<? extends V> layer, @NotNull final Point point) {
AffineTransform transform = this.getTransform(layer);
if (transform != null) {
try {
transform.inverseTransform(point, point);
} catch (NoninvertibleTransformException e) {
e.printStackTrace();
}
}
return point;
}
@Nullable
protected JXLayer<? extends V> getInstalledLayer() {
return installedLayer;
}
}

644
src/main/java/org/pbjar/jxlayer/plaf/ext/TransformUI.java

@ -0,0 +1,644 @@
package org.pbjar.jxlayer.plaf.ext;
/*
* Copyright (c) 2009, Piet Blok
* All rights reserved.
* <p>
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* <p>
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
* * Neither the name of the copyright holder nor the names of the
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
* <p>
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import com.sun.java.swing.SwingUtilities3;
import com.weis.darklaf.LogFormatter;
import org.jdesktop.jxlayer.JXLayer;
import org.jdesktop.jxlayer.plaf.AbstractBufferedLayerUI;
import org.jdesktop.jxlayer.plaf.LayerUI;
import org.jdesktop.swingx.ForwardingRepaintManager;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.pbjar.jxlayer.plaf.ext.transform.DefaultTransformModel;
import org.pbjar.jxlayer.plaf.ext.transform.TransformLayout;
import org.pbjar.jxlayer.plaf.ext.transform.TransformModel;
import org.pbjar.jxlayer.plaf.ext.transform.TransformRPMAnnotation;
import org.pbjar.jxlayer.plaf.ext.transform.TransformRPMFallBack;
import org.pbjar.jxlayer.plaf.ext.transform.TransformRPMSwingX;
import org.pbjar.jxlayer.repaint.RepaintManagerProvider;
import org.pbjar.jxlayer.repaint.RepaintManagerUtils;
import org.pbjar.jxlayer.repaint.WrappedRepaintManager;
import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.event.ChangeListener;
import javax.swing.text.GlyphView.GlyphPainter;
import javax.swing.text.JTextComponent;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.awt.image.BufferedImage;
import java.beans.PropertyChangeListener;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.logging.ConsoleHandler;
import java.util.logging.Logger;
/**
* This class provides for all necessary functionality when using transformations in a {@link
* LayerUI}.
*
* <p>Some implementation details:
*
* <p>
*
* <ul>
* <li>It extends {@link MouseEventUI} because, when applying transformations, the whereabouts of
* child components on screen (device space) do not necessarily match the location according
* to their bounds as set by layout managers (component space). So, mouse events must always
* be redirected to the intended recipients.
* <li>When enabled, this implementation sets a different {@link LayoutManager} to be used by
* {@link JXLayer}. Instead of setting the size of the view to {@link JXLayer}'s inner area,
* it sets the size of the view to the view's <em>preferred</em> size and centers it in the
* inner area. Also, when calculating the preferred size of {@link JXLayer}, it transforms the
* normally calculated size with the {@link AffineTransform} returned from {@link
* #getPreferredTransform(Dimension, JXLayer)}.
* <li>This implementation allocates a fresh {@link BufferedImage} the size of the clip area, each
* time that the {@link #paint(Graphics, JComponent)} method is invoked. This is different
* from the implementation of {@link AbstractBufferedLayerUI}, that maintains a cached image,
* the size of the view. An important reason to not follow the {@link AbstractBufferedLayerUI}
* strategy is that, when applying scaling transformations with a large scaling factor, a
* {@link OutOfMemoryError} may be thrown because it will try to allocate a buffer of an
* extreme size, even if not all of its contents will actually be visible on the screen.
* <li>Rather than configuring the screen graphics object, the image's graphics object is
* configured through {@link #configureGraphics(Graphics2D, JXLayer)}.
* <li>Regardless of whether or not the view is opaque, a background color is painted. It is
* obtained from the first component upwards in the hierarchy starting with the view, that is
* opaque. If an opaque component is not found, the background color of the layer is used.
* Painting the background is necessary to prevent visual artifacts when the transformation is
* changed dynamically.
* <li>Rendering hints may be set with {@link #setRenderingHints(Map)}, {@link
* #addRenderingHint(RenderingHints.Key, Object)} and {@link #addRenderingHints(Map)}.
* </ul>
*
* <p>Known limitations:
*
* <p>
*
* <ol>
* <li>In Java versions <b>before Java 6u10</b>, this implementation employs a custom {@link
* RepaintManager} in order to have descendant's repaint requests propagated up to the {@link
* JXLayer} ancestor. This {@link RepaintManager} will work well with and without other {@link
* RepaintManager} that are either subclasses of the {@link WrappedRepaintManager} or SwingX's
* {@link ForwardingRepaintManager}. Other {@link RepaintManager}s may cause conflicts.
* <p>In Java versions <b>6u10 or higher</b>, an attempt will be made to use the new
* RepaintManager delegate facility that has been designed for JavaFX.
* <li>Transformations will be applied on the whole of the content of the {@link JXLayer}. The
* result is that {@link Border}s and other content within {@link JXLayer}'s insets will
* generally either be invisible, or will be rendered in a very undesirable way. If you want a
* {@link Border} to be transformed together with {@link JXLayer}'s view, that border should
* be set on the view instead. On the other hand, if you want the {@link Border} not to be
* transformed, that border must be set on {@link JXLayer}'s parent.
* </ol>
*
* <b>Note:</b> A {@link TransformUI} instance cannot be shared and can be set to a single {@link
* JXLayer} instance only.
*
* @author Piet Blok
*/
public class TransformUI extends MouseEventUI<JComponent> {
private static final LayoutManager transformLayout = new TransformLayout();
private static final String KEY_VIEW = "view";
private static final boolean delegatePossible;
private static final RepaintManager wrappedManager = new TransformRepaintManager();
private static final Logger LOGGER = Logger.getLogger(TransformUI.class.getName());
static {
LOGGER.setUseParentHandlers(false);
ConsoleHandler handler = new ConsoleHandler();
handler.setFormatter(new LogFormatter());
LOGGER.addHandler(handler);
boolean value;
try {
SwingUtilities3.class.getMethod("setDelegateRepaintManager", JComponent.class, RepaintManager.class);
value = true;
} catch (Throwable t) {
value = false;
}
delegatePossible = value;
LOGGER.info("Java " + System.getProperty("java.version") + " " + System.getProperty("java.vm.version")
+ (delegatePossible ? ": RepaintManager delegate facility for JavaFX will be used."
: ": RepaintManager.setCurrentManager() will be used."));
}
private final ChangeListener changeListener = e -> revalidateLayer();
private final RepaintManagerProvider rpmProvider =
new RepaintManagerProvider() {
@NotNull
@Override
public Class<? extends ForwardingRepaintManager> getForwardingRepaintManagerClass() {
return TransformRPMSwingX.class;
}
@NotNull
@Override
public Class<? extends WrappedRepaintManager> getWrappedRepaintManagerClass() {
return TransformRPMFallBack.class;
}
@Override
public boolean isAdequate(@NotNull final Class<? extends RepaintManager> manager) {
return manager.isAnnotationPresent(TransformRPMAnnotation.class);
}
};
private final Map<RenderingHints.Key, Object> renderingHints = new HashMap<>();
private final Set<JComponent> originalDoubleBuffered = new HashSet<>();
private JComponent view;
private final PropertyChangeListener viewChangeListener =
evt -> setView((JComponent) evt.getNewValue());
@Nullable
private TransformModel transformModel;
private LayoutManager originalLayout;
/**
* Construct a {@link TransformUI} with a {@link DefaultTransformModel}.
*/
public TransformUI() {
this(new DefaultTransformModel());
}
/**
* Construct a {@link TransformUI} with a specified model.
*
* @param model the model
*/
public TransformUI(final TransformModel model) {
super();
this.setModel(model);
}
private void revalidateLayer() {
JXLayer<? extends JComponent> installedLayer = this.getInstalledLayer();
if (installedLayer != null) {
installedLayer.revalidate();
installedLayer.repaint();
}
}
/**
* {@link JTextComponent} and its descendants have some caret position problems when used inside a
* transformed {@link JXLayer}. When you plan to use {@link JTextComponent}(s) inside the
* hierarchy of a transformed {@link JXLayer}, call this method in an early stage, before
* instantiating any {@link JTextComponent}.
*
* <p>It executes the following method:
*
* <pre>
* System.setProperty(&quot;i18n&quot;, Boolean.TRUE.toString());
* </pre>
*
* <p>As a result, a {@link GlyphPainter} will be selected that uses floating point instead of
* fixed point calculations.
*/
public static void prepareForJTextComponent() {
System.setProperty("i18n", Boolean.TRUE.toString());
}
/**
* Add one rendering hint to the currently active rendering hints.
*
* @param key the key
* @param value the value
*/
public void addRenderingHint(final RenderingHints.Key key, final Object value) {
this.renderingHints.put(key, value);
}
/**
* Add new rendering hints to the currently active rendering hints.
*
* @param hints the new rendering hints
*/
public void addRenderingHints(@NotNull final Map<RenderingHints.Key, Object> hints) {
this.renderingHints.putAll(hints);
}
/**
* Get the {@link TransformModel}.
*
* @return the {@link TransformModel}
* @see #setModel(TransformModel)
*/
@Contract(pure = true)
@Nullable
public final TransformModel getModel() {
return transformModel;
}
/**
* Set a new {@link TransformModel}. The new model may not be {@code null}.
*
* @param transformModel the new model
* @throws NullPointerException if transformModel is {@code null}
* @see #getModel()
*/
@Contract("null -> fail")
public final void setModel(@Nullable final TransformModel transformModel) throws NullPointerException {
if (transformModel == null) {
throw new NullPointerException("The TransformModel may not be null");
}
if (this.transformModel != null) {
this.transformModel.removeChangeListener(this.changeListener);
}
this.transformModel = transformModel;
this.transformModel.addChangeListener(this.changeListener);
revalidateLayer();
}
/**
* Get a preferred {@link AffineTransform}. This method will typically be invoked by programs that
* calculate a preferred size.
*
* <p>The {@code size} argument will be used to compute anchor values for some types of
* transformations. If the {@code size} argument is {@code null} a value of (0,0) is used for the
* anchor.
*
* <p>In {@code enabled} state this method is delegated to the {@link TransformModel} that has
* been set. Otherwise {@code null} will be returned.
*
* @param size a {@link Dimension} instance to be used for an anchor or {@code null}
* @param layer the {@link JXLayer}.
* @return a {@link AffineTransform} instance or {@code null}
*/
@NotNull
public AffineTransform getPreferredTransform(
final Dimension size, final JXLayer<? extends JComponent> layer) {
return this.transformModel.getPreferredTransform(size, layer);
}
/*
{@inheritDoc}
<p>
This implementation does the following:
<ol>
<li>
A {@link BufferedImage} is created the size of the clip bounds of the
argument graphics object.</li>
<li>
A Graphics object is obtained from the image.</li>
<li>
The image is filled with a background color.</li>
<li>
The image graphics is translated according to x and y of the clip bounds.
</li>
<li>
The clip from the argument graphics object is set to the image graphics.</li>
<li>
{@link #configureGraphics(Graphics2D, JXLayer)} is invoked with the image
graphics as an argument.</li>
<li>
{@link #paintLayer(Graphics2D, JXLayer)} is invoked with the image
graphics as an argument.</li>
<li>
The image graphics is disposed.</li>
<li>
The image is drawn on the argument graphics object.</li>
</ol>
*/
/* @SuppressWarnings("unchecked")
@Override
public final void paint(Graphics g, JComponent component) {
Graphics2D g2 = (Graphics2D) g;
JXLayer<? extends JComponent> layer = (JXLayer<? extends JComponent>) component;
Shape clip = g2.getClip();
Rectangle clipBounds = g2.getClipBounds();
BufferedImage buffer = layer.getGraphicsConfiguration()
.createCompatibleImage(clipBounds.width, clipBounds.height,
Transparency.OPAQUE);//
Graphics2D g3 = buffer.createGraphics();
try {
g3.setColor(this.getBackgroundColor(layer));
g3.fillRect(0, 0, buffer.getWidth(), buffer.getHeight());
g3.translate(-clipBounds.x, -clipBounds.y);
g3.setClip(clip);
configureGraphics(g3, layer);
paintLayer(g3, layer);
} catch (Throwable t) {*/
/*
* Under some rare circumstances, the graphics engine may throw a
* transformation exception like this:
*
* sun.dc.pr.PRError: setPenT4: invalid pen transformation
* (singular)
*
* As far as I understand this happens when the result of the
* transformation has a zero sized surface.
*
* It will happen for example when shear X and shear Y are both set
* to 1.
*
* It will also happen when scale X or scale Y are set to 0.
*
* Since this Exception only seems to be thrown under the condition
* of a zero sized painting surface, no harm is done. Therefore the
* error logging below has been commented out, but remain in the
* source for the case that someone wants to investigate this
* phenomenon in more depth.
*
* The Exception however MUST be caught, not only to be able dispose
* the image's graphics object, but also to prevent that JXLayer
* enters a problematic state (the isPainting flag would not be
* reset).
*/
// System.err.println(t);
// AffineTransform at = g3.getTransform();
// System.err.println(at);
// System.err.println("scaleX = " + at.getScaleX() + " scaleY = "
// + at.getScaleY() + " shearX = " + at.getShearX()
// + " shearY = " + at.getShearY());
/*} finally {
g3.dispose();
}
g2.drawImage(buffer, clipBounds.x, clipBounds.y, null);
setDirty(false);
}*/
/**
* Overridden to replace the {@link LayoutManager}, to add some listeners and to ensure that an
* appropriate {@link RepaintManager} is installed.
*
* @see #uninstallUI(JComponent)
*/
@Override
public void installUI(@NotNull final JComponent component) {
super.installUI(component);
JXLayer<? extends JComponent> installedLayer = this.getInstalledLayer();
originalLayout = installedLayer.getLayout();
installedLayer.addPropertyChangeListener(KEY_VIEW, this.viewChangeListener);
installedLayer.setLayout(transformLayout);
setView(installedLayer.getView());
if (!delegatePossible) {
RepaintManagerUtils.ensureRepaintManagerSet(installedLayer, rpmProvider);
}
}
/**
* Overridden to restore the original {@link LayoutManager} and remove some listeners.
*
* @param c the component.
*/
@Override
public void uninstallUI(@NotNull final JComponent c) {
JXLayer<? extends JComponent> installedLayer = this.getInstalledLayer();
Objects.requireNonNull(installedLayer)
.removePropertyChangeListener(KEY_VIEW, this.viewChangeListener);
installedLayer.setLayout(originalLayout);
setView(null);
super.uninstallUI(c);
}
private void setView(final JComponent view) {
if (delegatePossible) {
if (this.view != null) {
SwingUtilities3.setDelegateRepaintManager(this.view, null);
}
}
this.view = view;
if (delegatePossible) {
if (this.view != null) {
SwingUtilities3.setDelegateRepaintManager(this.view, wrappedManager);
}
}
setDirty(true);
}
/**
* Replace the currently active rendering hints with new hints.
*
* @param hints the new rendering hints or {@code null} to clear all rendering hints
*/
public void setRenderingHints(@Nullable final Map<RenderingHints.Key, Object> hints) {
this.renderingHints.clear();
if (hints != null) {
this.renderingHints.putAll(hints);
}
}
/**
* Primarily intended for use by {@link RepaintManager}.
*
* @param rect a rectangle
* @param layer the layer
* @return the argument rectangle if no {@link AffineTransform} is available, else a new rectangle
*/
public final Rectangle transform(@NotNull final Rectangle rect, final JXLayer<? extends JComponent> layer) {
AffineTransform at = getTransform(layer);
if (at == null) {
return rect;
} else {
Area area = new Area(rect);
area.transform(at);
return area.getBounds();
}
}
/**
* Mark {@link TransformUI} as dirty if the LookAndFeel was changed.
*
* @param layer the {@link JXLayer} this {@link TransformUI} is set to
*/
@Override
public void updateUI(final JXLayer<? extends JComponent> layer) {
setDirty(true);
}
/*
* Get the most suitable background color.
*/
private Color getBackgroundColor(@NotNull final JXLayer<? extends JComponent> layer) {
Container colorProvider = layer.getView() == null ? layer : layer.getView();
while (colorProvider != null && !colorProvider.isOpaque()) {
colorProvider = colorProvider.getParent();
}
return colorProvider == null ? SystemColor.desktop : colorProvider.getBackground();
}
/**
* Set a complete hierarchy to non double buffered and remember the components that were double
* buffered.
*
* @param component the component.
*/
private void setToNoDoubleBuffering(final Component component) {
if (component instanceof JComponent) {
JComponent comp = (JComponent) component;
if (comp.isDoubleBuffered()) {
originalDoubleBuffered.add(comp);
comp.setDoubleBuffered(false);
}
}
if (component instanceof Container) {
Container container = (Container) component;
for (int index = 0; index < container.getComponentCount(); index++) {
setToNoDoubleBuffering(container.getComponent(index));
}
}
}
/**
* If the view of the {@link JXLayer} is (partly) obscured by its parent (this is the case when
* the size of the view (in component space) is larger than the size of the {@link JXLayer}), the
* obscured parts will not be painted by the super implementation. Therefore, only under this
* condition, a special painting technique is executed:
*
* <ol>
* <li>All descendants of the {@link JXLayer} are temporarily set to non double buffered.
* <li>The graphics object is translated for the X and Y coordinates of the view.
* <li>The view is painted.
* <li>The original double buffered property is restored for all descendants.
* </ol>
*
* <p>In all other cases, the super method is invoked.
*
* <p>The {@code g2} argument is a graphics object obtained from a {@link BufferedImage}.
*
* @see #paint(Graphics, JComponent)
*/
@Override
protected final void paintLayer(
@NotNull final Graphics2D g2, @NotNull final JXLayer<? extends JComponent> layer) {
JComponent view = layer.getView();
if (view != null) {
if (view.getX() < 0 || view.getY() < 0) {
setToNoDoubleBuffering(view);
g2.translate(view.getX(), view.getY());
view.paint(g2);
for (JComponent comp : originalDoubleBuffered) {
comp.setDoubleBuffered(true);
}
originalDoubleBuffered.clear();
return;
}
}
super.paintLayer(g2, layer);
}
/**
* Get the {@link AffineTransform} customized for the {@code layer} argument.
*
* <p>In {@code enabled} state this method is delegated to the {@link TransformModel} that has
* been set. Otherwise {@code null} will be returned.
*/
@NotNull
@Override
protected final AffineTransform getTransform(final JXLayer<? extends JComponent> layer) {
return transformModel.getTransform(layer);
}
/**
* Get the rendering hints.
*
* @return the rendering hints
* @see #setRenderingHints(Map)
* @see #addRenderingHints(Map)
* @see #addRenderingHint(RenderingHints.Key, Object)
*/
@NotNull
@Override
protected Map<RenderingHints.Key, Object> getRenderingHints(final JXLayer<? extends JComponent> layer) {
return renderingHints;
}
/**
* A delegate {@link RepaintManager} that can be set on the view of a {@link JXLayer} in Java
* versions starting with Java 6u10.
*
* <p>For older Java versions, {@link RepaintManager#setCurrentManager(RepaintManager)} will be
* used with either {@link TransformRPMFallBack} or {@link TransformRPMSwingX}.
*/
protected static final class TransformRepaintManager extends RepaintManager {
private TransformRepaintManager() {
}
/**
* Finds the JXLayer ancestor and have ancestor marked invalid via the current {@link
* RepaintManager}.
*/
@Override
public void addInvalidComponent(final JComponent invalidComponent) {
JXLayer<? extends JComponent> layer = findJXLayer(invalidComponent);
RepaintManager.currentManager(layer).addInvalidComponent(layer);
}
/**
* Finds the JXLayer ancestor and have the ancestor marked as dirty with the transformed
* rectangle via the current {@link RepaintManager}.
*/
@Override
public void addDirtyRegion(@NotNull final JComponent c, final int x, final int y, final int w, final int h) {
if (c.isShowing()) {
JXLayer<? extends JComponent> layer = findJXLayer(c);
TransformUI ui = (TransformUI) layer.getUI();
Point point = c.getLocationOnScreen();
SwingUtilities.convertPointFromScreen(point, layer);
Rectangle transformPortRegion = ui.transform(new Rectangle(x + point.x, y + point.y, w, h),
layer);
RepaintManager.currentManager(layer).addDirtyRegion(layer,
transformPortRegion.x,
transformPortRegion.y,
transformPortRegion.width,
transformPortRegion.height);
}
}
/**
* Find the ancestor {@link JXLayer} instance.
*
* @param c a component
* @return the ancestor {@link JXLayer} instance
*/
@NotNull
@SuppressWarnings("unchecked")
private JXLayer<? extends JComponent> findJXLayer(final JComponent c) {
JXLayer<?> layer = (JXLayer<?>) SwingUtilities.getAncestorOfClass(JXLayer.class, c);
if (layer != null) {
LayerUI<?> layerUI = layer.getUI();
if (layerUI instanceof TransformUI) {
return (JXLayer<? extends JComponent>) layer;
} else {
return findJXLayer(layer);
}
}
throw new Error("No parent JXLayer with TransformUI found");
}
}
}

116
src/main/java/org/pbjar/jxlayer/plaf/ext/transform/DefaultLayerLayout.java

@ -0,0 +1,116 @@
package org.pbjar.jxlayer.plaf.ext.transform;
/*
* Copyright (c) 2009, Piet Blok
* All rights reserved.
* <p>
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* <p>
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
* * Neither the name of the copyright holder nor the names of the
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
* <p>
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import org.jdesktop.jxlayer.JXLayer;
import org.jetbrains.annotations.NotNull;
import java.awt.*;
import java.io.Serializable;
/**
* A copy of the private static inner class in JXLayer.
*
* @author Piet Blok
*/
public class DefaultLayerLayout implements LayoutManager, Serializable {
/**
* {@inheritDoc}
*/
public void addLayoutComponent(final String name, final Component comp) {
}
/**
* {@inheritDoc}
*/
public void removeLayoutComponent(final Component comp) {
}
/**
* {@inheritDoc}
*/
@NotNull
public Dimension preferredLayoutSize(final Container parent) {
JXLayer<?> layer = (JXLayer<?>) parent;
Insets insets = layer.getInsets();
Dimension ret = new Dimension(insets.left + insets.right, insets.top + insets.bottom);
Component view = layer.getView();
if (view != null) {
Dimension size = view.getPreferredSize();
if (size.width > 0 && size.height > 0) {
ret.width += size.width;
ret.height += size.height;
}
}
return ret;
}
/**
* {@inheritDoc}
*/
@NotNull
public Dimension minimumLayoutSize(final Container parent) {
JXLayer<?> layer = (JXLayer<?>) parent;
Insets insets = layer.getInsets();
Dimension ret = new Dimension(insets.left + insets.right, insets.top + insets.bottom);
Component view = layer.getView();
if (view != null) {
Dimension size = view.getMinimumSize();
ret.width += size.width;
ret.height += size.height;
}
if (ret.width == 0 || ret.height == 0) {
ret.width = 4;
ret.height = 4;
}
return ret;
}
/**
* {@inheritDoc}
*/
public void layoutContainer(final Container parent) {
JXLayer<?> layer = (JXLayer<?>) parent;
Component view = layer.getView();
Component glassPane = layer.getGlassPane();
if (view != null) {
Insets insets = layer.getInsets();
view.setLocation(insets.left, insets.top);
view.setSize(
layer.getWidth() - insets.left - insets.right,
layer.getHeight() - insets.top - insets.bottom);
}
if (glassPane != null) {
glassPane.setLocation(0, 0);
glassPane.setSize(layer.getWidth(), layer.getHeight());
}
}
}

495
src/main/java/org/pbjar/jxlayer/plaf/ext/transform/DefaultTransformModel.java

@ -0,0 +1,495 @@
package org.pbjar.jxlayer.plaf.ext.transform;
/*
* Copyright (c) 2009, Piet Blok All rights reserved.
* <p>
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* <p>
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer. * Redistributions in
* binary form must reproduce the above copyright notice, this list of
* conditions and the following disclaimer in the documentation and/or other
* materials provided with the distribution. * Neither the name of the copyright
* holder nor the names of the contributors may be used to endorse or promote
* products derived from this software without specific prior written
* permission.
* <p>
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
import org.jdesktop.jxlayer.JXLayer;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.Arrays;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.function.Function;
/**
* This is an implementation of {@link TransformModel} with methods to explicitly set transformation
* values.
*
* @author Piet Blok
*/
public class DefaultTransformModel implements TransformModel {
private final Map<ChangeListener, Object> listeners = new WeakHashMap<>();
/**
* The transform object that will be recalculated upon any change.
*/
private final AffineTransform transform = new AffineTransform();
/**
* Is populated with the current values.
*/
private final Object[] values = Type.createArray();
/**
* Is populated with the previous values.
*/
private final Object[] prevValues = Type.createArray();
private Point2D rotationCenter;
private Function<Dimension, Point2D> supplier;
private boolean valid = false;
@Override
public void addChangeListener(final ChangeListener listener) {
listeners.put(listener, null);
}
@NotNull
@Override
public AffineTransform getPreferredTransform(@Nullable final Dimension size, final JXLayer<?> layer) {
var p = getRotationCenter(size);
double centerX = p.getX();
double centerY = p.getY();
AffineTransform transform = transformNoScale(centerX, centerY);
double scaleX = getValue(Type.PreferredScale);
transform.translate(centerX, centerY);
transform.scale(getValue(Type.Mirror) ? -scaleX : scaleX, scaleX);
transform.translate(-centerX, -centerY);
return transform;
}
/**
* Get the rotation center corresponding to the size.
*
* @param size the size.
* @return center of rotation.
*/
public Point2D getRotationCenter(@Nullable final Dimension size) {
if (supplier != null) {
return supplier.apply(size);
}
double centerX = size == null ? 0 : size.getWidth() / 2.0;
double centerY = size == null ? 0 : size.getHeight() / 2.0;
return rotationCenter == null ? new Point2D.Double(centerX, centerY) : rotationCenter;
}
/**
* Apply the prescribed transformations, excluding the scale.
*
* @param centerX a center X
* @param centerY a center Y
* @return a new {@link AffineTransform}
*/
@NotNull
protected AffineTransform transformNoScale(final double centerX, final double centerY) {
AffineTransform at = new AffineTransform();
at.translate(centerX, centerY);
at.rotate(getRotation());
at.quadrantRotate(getQuadrantRotation());
at.shear(getShearX(), getShearY());
at.translate(-centerX, -centerY);
return at;
}
/**
* Get the rotation value in radians as set by {@link #setRotation(double)}. The default value is
* {@code 0}.
*
* @return the rotation value.
* @see #setRotation(double)
*/
public double getRotation() {
return (Double) getValue(Type.Rotation);
}
/**
* Get the quadrant rotation value. The default value is {@code 0}.
*
* @return the quadrant rotation value
* @see #setQuadrantRotation(int)
*/
public int getQuadrantRotation() {
return (Integer) getValue(Type.QuadrantRotation);
}
/**
* Set the rotation in quadrants. The default value is {@code 0}.
*
* @param newValue the number of quadrants
* @see #getQuadrantRotation()
*/
public void setQuadrantRotation(final int newValue) {
setValue(Type.QuadrantRotation, newValue);
}
/**
* Get the shearX value as set by {@link #setShearX(double)}; The default value is {@code 0}.
*
* @return the shear x value
* @see #setShearX(double)
*/
public double getShearX() {
return (Double) getValue(Type.ShearX);
}
/**
* Set the shearX value. The default value is {@code 0}.
*
* @param newValue the shear x
* @see #getShearX()
*/
public void setShearX(final double newValue) {
setValue(Type.ShearX, newValue);
}
/**
* Get the shearY value as set by {@link #setShearY(double)}; The default value is {@code 0}.
*
* @return the shear y value
* @see #setShearY(double)
*/
public double getShearY() {
return (Double) getValue(Type.ShearY);
}
/**
* Set the shearY value. The default value is {@code 0}.
*
* @param newValue the shear y
* @see #getShearY()
*/
public void setShearY(final double newValue) {
setValue(Type.ShearY, newValue);
}
/**
* Set the rotation in radians. The default value is {@code 0}.
*
* @param newValue the rotation in radians
* @see #getRotation()
*/
public void setRotation(final double newValue) {
setValue(Type.Rotation, newValue);
}
/**
* Return the currently active {@link AffineTransform}. Recalculate if needed.
*
* @return the currently active {@link AffineTransform}
*/
@NotNull
@Override
public AffineTransform getTransform(@Nullable final JXLayer<? extends JComponent> layer) {
JComponent view = layer == null ? null : layer.getView();
/*
* Set the current actual program values in addition to the user options.
*/
setValue(Type.LayerWidth, layer == null ? 0 : layer.getWidth());
setValue(Type.LayerHeight, layer == null ? 0 : layer.getHeight());
setValue(Type.ViewWidth, view == null ? 0 : view.getWidth());
setValue(Type.ViewHeight, view == null ? 0 : view.getHeight());
/*
* If any change to previous values, recompute the transform.
*/
if (!Arrays.equals(prevValues, values) || !valid) {
System.arraycopy(values, 0, prevValues, 0, values.length);
transform.setToIdentity();
if (view != null) {
var p = getRotationCenter(layer == null ? null : layer.getSize());
double centerX = p.getX();
double centerY = p.getY();
AffineTransform nonScaledTransform = transformNoScale(centerX, centerY);
double scaleX;
double scaleY;
if (getValue(Type.ScaleToPreferredSize)) {
scaleX = getValue(Type.PreferredScale);
scaleY = scaleX;
} else {
Area area = new Area(new Rectangle2D.Double(0, 0, view.getWidth(), view.getHeight()));
area.transform(nonScaledTransform);
Rectangle2D bounds = area.getBounds2D();
scaleX = layer == null ? 0 : layer.getWidth() / bounds.getWidth();
scaleY = layer == null ? 0 : layer.getHeight() / bounds.getHeight();
if (getValue(Type.PreserveAspectRatio)) {
scaleX = Math.min(scaleX, scaleY);
scaleY = scaleX;
}
}
transform.translate(centerX, centerY);
transform.scale(getValue(Type.Mirror) ? -scaleX : scaleX, scaleY);
transform.translate(-centerX, -centerY);
transform.concatenate(nonScaledTransform);
}
}
valid = true;
return transform;
}
@Override
public void removeChangeListener(final ChangeListener listener) {
listeners.remove(listener);
}
/**
* Get the scale.
*
* @return the scale
* @see #setScale(double)
*/
public double getScale() {
return (Double) getValue(Type.PreferredScale);
}
/**
* Set a scale.
*
* <p>The scale is primarily used to calculate a preferred size. Unless {@code
* ScaleToPreferredSize} is set to {@code true} (see {@link #setScaleToPreferredSize(boolean)} and
* {@link #isScaleToPreferredSize()}), actual scaling itself is calculated such that the view
* occupies as much space as possible on the {@link JXLayer}.
*
* <p>The default value is 1.
*
* @param newValue the preferred scale
* @throws IllegalArgumentException when the argument value is 0
* @see #getScale()
*/
public void setScale(final double newValue) throws IllegalArgumentException {
if (newValue == 0.0) {
throw new IllegalArgumentException("Preferred scale can not be set to 0");
}
setValue(Type.PreferredScale, newValue);
}
/**
* Set a value and fire a PropertyChange.
*
* @param type the value type
* @param newValue the new value
*/
private void setValue(@NotNull final Type type, final Object newValue) {
Object oldValue = values[type.ordinal()];
values[type.ordinal()] = newValue;
fireChangeEvent(oldValue, newValue);
}
/**
* If {!oldValue.equals(newValue)}, a {@link ChangeEvent} will be fired.
*
* @param oldValue an old value
* @param newValue a new value
*/
protected void fireChangeEvent(@NotNull final Object oldValue, final Object newValue) {
if (!oldValue.equals(newValue)) {
ChangeEvent event = new ChangeEvent(this);
for (ChangeListener listener : listeners.keySet()) {
listener.stateChanged(event);
}
}
}
@NotNull
@SuppressWarnings("unchecked")
protected <T> T getValue(@NotNull final Type type) {
return (T) values[type.ordinal()];
}
public void setRotationCenter(final Point2D rotationCenter) {
this.rotationCenter = rotationCenter;
}
/**
* Get the mirror property.
*
* <p>The default value is {@code false}.
*
* @return {@code true} if the transformation will mirror the view.
* @see #setMirror(boolean)
*/
public boolean isMirror() {
return (Boolean) getValue(Type.Mirror);
}
/**
* Set the mirror property.
*
* <p>The default value is {@code false}
*
* @param newValue the new value
* @see #isMirror()
*/
public void setMirror(final boolean newValue) {
setValue(Type.Mirror, newValue);
}
/**
* Get the preserve aspect ratio value.
*
* <p>The default value is {@code true}.
*
* @return {@code true} if preserving aspect ratio, {@code false} otherwise
* @see #setPreserveAspectRatio(boolean)
*/
public boolean isPreserveAspectRatio() {
return (Boolean) getValue(Type.PreserveAspectRatio);
}
/**
* Set preserve aspect ratio.
*
* <p>The default value is {@code true}.
*
* @param newValue the new value
* @see #isPreserveAspectRatio()
*/
public void setPreserveAspectRatio(final boolean newValue) {
setValue(Type.PreserveAspectRatio, newValue);
}
/**
* Get the scale to preferred size value.
*
* <p>The default value is {@code false}.
*
* <p>When {@code true}, the view is scaled according to the preferred scale, regardless of the
* size of the {@link JXLayer}.
*
* <p>When {@code false}, the view is scaled to occupy as much as possible of the size of the
* {@link JXLayer}.
*
* @return {@code true} if scale to preferred size, {@code false} otherwise
* @see #setScaleToPreferredSize(boolean)
*/
public boolean isScaleToPreferredSize() {
return (Boolean) getValue(Type.ScaleToPreferredSize);
}
/**
* Set scaleToPreferredSize.
*
* <p>The default value is {@code false}.
*
* <p>When {@code true}, the view is scaled according to the preferred scale, regardless of the
* size of the {@link JXLayer}.
*
* <p>When {@code false}, the view is scaled to occupy as much as possible of the size of the
* {@link JXLayer}.
*
* @param newValue the new value
* @see #isScaleToPreferredSize()
*/
public void setScaleToPreferredSize(final boolean newValue) {
setValue(Type.ScaleToPreferredSize, newValue);
}
/**
* Set the supplier for calculating the center of rotation.
*
* @param supplier the supplier.
*/
public void setRotationCenterSupplier(final Function<Dimension, Point2D> supplier) {
{
this.supplier = supplier;
}
}
/**
* Force the translation to be recalculated the next time it is needed.
*/
public void invalidate() {
valid = false;
}
/**
* Enum for internal convenience.
*
* <p>Describes the values that on change trigger recalculation of the transform. All have a
* default value, used for initializing arrays.
*
* <p>These enums are used for two purposes:
*
* <p>1: To easily detect a change that requires renewed calculation of the transform(both program
* values and user options).
*
* <p>2: To generalize setters (both program values and user options) and getters (only
* userOptions) for the various values.
*
* <p>There are two groups:
*
* <p>1: Program values that reflect the current size etc. of affected components
*
* <p>2: User options
*/
protected enum Type {
/*
* Program values
*/
LayerWidth(0),
LayerHeight(0),
ViewWidth(0),
ViewHeight(0),
/*
* User options
*/
PreferredScale(1.0),
Rotation(0.0),
ShearX(0.0),
ShearY(0.0),
QuadrantRotation(0),
PreserveAspectRatio(Boolean.TRUE),
ScaleToPreferredSize(Boolean.FALSE),
Mirror(Boolean.FALSE);
private final Object defaultValue;
@Contract(pure = true)
Type(final Object defaultValue) {
this.defaultValue = defaultValue;
}
@NotNull
public static Object[] createArray() {
Object[] array = new Object[values().length];
for (Type type : values()) {
array[type.ordinal()] = type.defaultValue;
}
return array;
}
}
}

140
src/main/java/org/pbjar/jxlayer/plaf/ext/transform/TransformLayout.java

@ -0,0 +1,140 @@
/*
Copyright (c) 2009, Piet Blok
All rights reserved.
<p>
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
<p>
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* Neither the name of the copyright holder nor the names of the
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
<p>
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.pbjar.jxlayer.plaf.ext.transform;
import org.jdesktop.jxlayer.JXLayer;
import org.jdesktop.jxlayer.plaf.LayerUI;
import org.jetbrains.annotations.NotNull;
import org.pbjar.jxlayer.plaf.ext.TransformUI;
import javax.swing.*;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.awt.geom.Rectangle2D;
/**
* A specialized layout manager for {@link JXLayer} in combination with the {@link TransformUI}.
*
* <p>It extends {@link DefaultLayerLayout} and, as long as no enabled {@link TransformUI} is set to
* {@link JXLayer}, will act exactly the same as its super class.
*
* <p>However, when the above conditions are all true, its behavior becomes different:
*
* <ol>
* <li>Instead of setting the view's size to the layer's calculated inner area, it will set the
* view's size to its preferred size.
* <li>Instead of setting the view's bounds to the calculated inner area, it will center the view
* in that inner area. This may result in some parts of the view formally obscured, or, some
* parts of the inner area not covered by the view.
* <li>The preferred size will first be computed by the super implementation. Then, before
* returning, the calculated size will be transformed with the {@link AffineTransform}
* returned by {@link TransformUI#getPreferredTransform(Dimension, JXLayer)};
* <li>The minimum size will first be computed by the super implementation. Then, before
* returning, the calculated size will be transformed with the {@link AffineTransform}
* returned by {@link TransformUI#getPreferredTransform(Dimension, JXLayer)};
* </ol>
*
* @see JXLayer#getView()
* @see JXLayer#getGlassPane()
* @see TransformUI
*/
public class TransformLayout extends DefaultLayerLayout {
/**
* Overridden to apply a different layout when the {@link LayerUI} is an instance of {@link
* TransformUI}. If this is not the case, the super implementation will be invoked.
*/
@Override
public void layoutContainer(final Container parent) {
JXLayer<?> layer = (JXLayer<?>) parent;
LayerUI<?> layerUI = layer.getUI();
if (layerUI instanceof TransformUI) {
JComponent view = (JComponent) layer.getView();
JComponent glassPane = layer.getGlassPane();
if (view != null) {
Rectangle innerArea = new Rectangle();
SwingUtilities.calculateInnerArea(layer, innerArea);
view.setSize(view.getPreferredSize());
Rectangle viewRect = new Rectangle(0, 0, view.getWidth(), view.getHeight());
int x = (int) Math.round(innerArea.getCenterX() - viewRect.getCenterX());
int y = (int) Math.round(innerArea.getCenterY() - viewRect.getCenterY());
viewRect.translate(x, y);
view.setBounds(viewRect);
}
if (glassPane != null) {
glassPane.setLocation(0, 0);
glassPane.setSize(layer.getWidth(), layer.getHeight());
}
return;
}
super.layoutContainer(parent);
}
/**
* Overridden to apply a preferred transform on the {@link Dimension} object returned from the
* super implementation.
*/
@NotNull
@Override
public Dimension minimumLayoutSize(final Container parent) {
return transform(parent, super.minimumLayoutSize(parent));
}
/**
* Overridden to apply a preferred transform on the {@link Dimension} object returned from the
* super implementation.
*/
@NotNull
@Override
public Dimension preferredLayoutSize(final Container parent) {
return transform(parent, super.preferredLayoutSize(parent));
}
@NotNull
@SuppressWarnings("unchecked")
private Dimension transform(final Container parent, @NotNull final Dimension size) {
JXLayer<JComponent> layer = (JXLayer<JComponent>) parent;
LayerUI<?> ui = layer.getUI();
if (ui instanceof TransformUI) {
TransformUI transformUI = (TransformUI) ui;
AffineTransform transform = transformUI.getPreferredTransform(size, layer);
if (transform != null) {
Area area = new Area(new Rectangle2D.Double(0, 0, size.getWidth(), size.getHeight()));
area.transform(transform);
Rectangle2D bounds = area.getBounds2D();
size.setSize(bounds.getWidth(), bounds.getHeight());
}
}
return size;
}
}

92
src/main/java/org/pbjar/jxlayer/plaf/ext/transform/TransformModel.java

@ -0,0 +1,92 @@
/*
Copyright (c) 2009, Piet Blok
All rights reserved.
<p>
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
<p>
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* Neither the name of the copyright holder nor the names of the
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
<p>
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.pbjar.jxlayer.plaf.ext.transform;
import org.jdesktop.jxlayer.JXLayer;
import org.jetbrains.annotations.NotNull;
import org.pbjar.jxlayer.plaf.ext.TransformUI;
import javax.swing.*;
import javax.swing.event.ChangeListener;
import java.awt.*;
import java.awt.geom.AffineTransform;
/**
* The {@link TransformModel} interface specifies the methods the {@link TransformUI} will use to
* interrogate a transformation model.
*
* @author Piet Blok
*/
public interface TransformModel {
/**
* Add a {@link ChangeListener} that will be notified when the internal state of this model
* changes.
*
* @param listener a {@link ChangeListener}
* @see #removeChangeListener(ChangeListener)
*/
void addChangeListener(ChangeListener listener);
/**
* Get a preferred {@link AffineTransform}. This method will typically be invoked by programs that
* calculate a preferred size.
*
* <p>The {@code size} argument will be used to compute anchor values for some types of
* transformations. If the {@code size} argument is {@code null} a value of (0,0) is used for the
* anchor.
*
* @param size a {@link Dimension} instance to be used for an anchor or {@code null}
* @param layer the {@link JXLayer}.
* @return a {@link AffineTransform} instance or {@code null}
*/
@NotNull
AffineTransform getPreferredTransform(Dimension size, JXLayer<?> layer);
/**
* Get a {@link AffineTransform}. This method will typically be invoked by programs that are about
* to prepare a {@link Graphics} object.
*
* @param layer the {@link JXLayer}
* @return a {@link AffineTransform} or {@code null}
*/
@NotNull
AffineTransform getTransform(JXLayer<? extends JComponent> layer);
/**
* Remove a {@link ChangeListener}.
*
* @param listener a {@link ChangeListener}
* @see #addChangeListener(ChangeListener)
*/
void removeChangeListener(ChangeListener listener);
}

50
src/main/java/org/pbjar/jxlayer/plaf/ext/transform/TransformRPMAnnotation.java

@ -0,0 +1,50 @@
/*
Copyright (c) 2009, Piet Blok
All rights reserved.
<p>
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
<p>
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* Neither the name of the copyright holder nor the names of the
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
<p>
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.pbjar.jxlayer.plaf.ext.transform;
import org.pbjar.jxlayer.plaf.ext.TransformUI;
import javax.swing.*;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* A marker for an adequate {@link RepaintManager} for the {@link TransformUI}.
*
* @author Piet Blok
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface TransformRPMAnnotation {
}

76
src/main/java/org/pbjar/jxlayer/plaf/ext/transform/TransformRPMFallBack.java

@ -0,0 +1,76 @@
/*
Copyright (c) 2008-2009, Piet Blok
All rights reserved.
<p>
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
<p>
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* Neither the name of the copyright holder nor the names of the
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
<p>
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.pbjar.jxlayer.plaf.ext.transform;
import org.jdesktop.swingx.ForwardingRepaintManager;
import org.jetbrains.annotations.NotNull;
import org.pbjar.jxlayer.repaint.RepaintManagerProvider;
import org.pbjar.jxlayer.repaint.RepaintManagerUtils;
import org.pbjar.jxlayer.repaint.WrappedRepaintManager;
import javax.swing.*;
/**
* A specialized {@link RepaintManager} that checks for every JComponent that is being set dirty, if
* it has a JXLayer ancestor, equipped with a TransformUI. In that case, the transformed region on
* the JXLayer is also marked dirty.
*
* <p>A fall back class if the {@link ForwardingRepaintManager} cannot be instantiated because the
* SwingX packages are not on the class path.
*
* @see RepaintManagerProvider
* @see RepaintManagerUtils
* @see TransformRPMSwingX
*/
@TransformRPMAnnotation
public class TransformRPMFallBack extends WrappedRepaintManager {
/**
* Sole constructor.
*
* @param delegate the delegate {@link RepaintManager}
*/
public TransformRPMFallBack(final RepaintManager delegate) {
super(delegate);
TransformRPMImpl.hackInitialization(delegate, this);
}
/**
* Delegates and then marks a JXLayer ancestor as dirty with the transformed rectangle.
*/
@Override
public void addDirtyRegion(@NotNull final JComponent c, final int x, final int y, final int w, final int h) {
if (!TransformRPMImpl.addDirtyRegion(c, x, y, w, h, this)) {
super.addDirtyRegion(c, x, y, w, h);
}
}
}

197
src/main/java/org/pbjar/jxlayer/plaf/ext/transform/TransformRPMImpl.java

@ -0,0 +1,197 @@
/*
Copyright (c) 2009, Piet Blok
All rights reserved.
<p>
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
<p>
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* Neither the name of the copyright holder nor the names of the
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
<p>
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.pbjar.jxlayer.plaf.ext.transform;
import com.weis.darklaf.LogFormatter;
import org.jdesktop.jxlayer.JXLayer;
import org.jdesktop.jxlayer.plaf.LayerUI;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.pbjar.jxlayer.plaf.ext.TransformUI;
import javax.swing.*;
import java.awt.*;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.logging.ConsoleHandler;
import java.util.logging.Logger;
/**
* To avoid duplicate code, this class implements the actual logic for {@link TransformRPMSwingX}
* and {@link TransformRPMFallBack}.
*
* @author Piet Blok
*/
public final class TransformRPMImpl {
private static final Logger LOGGER = Logger.getLogger(TransformRPMImpl.class.getName());
/**
* A flag, indicating whether or not a very dirty initialization on created {@link
* RepaintManager}s must be performed.
*
* @see #hackInitialization(RepaintManager, RepaintManager)
*/
public static boolean hack = false;
static {
LOGGER.setUseParentHandlers(false);
ConsoleHandler handler = new ConsoleHandler();
handler.setFormatter(new LogFormatter());
LOGGER.addHandler(handler);
}
private TransformRPMImpl() {
}
/**
* Searches upwards in the component hierarchy for a {@link JXLayer} ancestor with an enabled
* {@link TransformUI}.
* <p>
* If found, the dirty rectangle is transformed to a rectangle targeted at that {@link JXLayer}
* and the argument manager's {@link RepaintManager#addDirtyRegion(JComponent, int, int, int,
* int)} is invoked. {@code true} is returned.
* </p>
* <p>
* Else, (@code false} is returned.
* </p>
*
* @param aComponent a component
* @param x the X of the dirty region
* @param y the Y of the dirty region
* @param w the width of the dirty region
* @param h the height of the dirty region
* @param manager the current {@link RepaintManager}
* @return {@code true} if the call is delegated to the manager with a transformed rectangle,
* {@code false} otherwise
*/
@SuppressWarnings("unchecked")
public static boolean addDirtyRegion(@NotNull final JComponent aComponent, final int x, final int y,
final int w, final int h, @NotNull final RepaintManager manager) {
if (aComponent.isShowing()) {
JXLayer<?> layer = findJXLayer(aComponent);
if (layer != null) {
LayerUI<?> layerUI = layer.getUI();
TransformUI ui = (TransformUI) layerUI;
Point point = aComponent.getLocationOnScreen();
SwingUtilities.convertPointFromScreen(point, layer);
Rectangle transformPortRegion = ui.transform(new Rectangle(x + point.x, y + point.y, w, h),
(JXLayer<JComponent>) layer);
manager.addDirtyRegion(layer,
transformPortRegion.x, transformPortRegion.y,
transformPortRegion.width, transformPortRegion.height);
return true;
}
}
return false;
}
/**
* If {@link #hack} is {@code true}, the private fields {@code paintManager} and {@code
* bufferStrategyType} are copied via reflection from the source manager into the destination
* manager.
*
* @param sourceManager the source manager
* @param destinationManager the destination manager
*/
public static void hackInitialization(final RepaintManager sourceManager,
final RepaintManager destinationManager) {
if (hack) {
Class<RepaintManager> rpmClass = RepaintManager.class;
try {
Field fieldBufferStrategyType = rpmClass.getDeclaredField("bufferStrategyType");
Field fieldPaintManager = rpmClass.getDeclaredField("paintManager");
Method methodGetPaintManager = rpmClass.getDeclaredMethod("getPaintManager");
fieldBufferStrategyType.setAccessible(true);
fieldPaintManager.setAccessible(true);
methodGetPaintManager.setAccessible(true);
Object paintManager = methodGetPaintManager.invoke(sourceManager);
short bufferStrategyType = (Short) fieldBufferStrategyType.get(sourceManager);
fieldBufferStrategyType.set(destinationManager, bufferStrategyType);
fieldPaintManager.set(destinationManager, paintManager);
fieldBufferStrategyType.setAccessible(false);
fieldPaintManager.setAccessible(false);
methodGetPaintManager.setAccessible(false);
LOGGER.warning("Copied paintManager of type: " + paintManager.getClass().getName());
switch (bufferStrategyType) {
case (0):
LOGGER.warning("Copied bufferStrategyType "
+ bufferStrategyType
+ ": BUFFER_STRATEGY_NOT_SPECIFIED");
break;
case (1):
LOGGER.warning("Copied bufferStrategyType "
+ bufferStrategyType
+ ": BUFFER_STRATEGY_SPECIFIED_ON");
break;
case (2):
LOGGER.warning("Copied bufferStrategyType "
+ bufferStrategyType
+ ": BUFFER_STRATEGY_SPECIFIED_OFF");
break;
default:
LOGGER.warning("Copied bufferStrategyType "
+ bufferStrategyType + ": ???");
break;
}
} catch (Throwable t) {
t.printStackTrace(System.out);
}
}
}
/**
* Find the first ancestor {@link JXLayer} with an enabled {@link TransformUI}.
*
* @param aComponent some component
* @return a {@link JXLayer} instance or {@code null}
*/
@Nullable
private static JXLayer<?> findJXLayer(final JComponent aComponent) {
JXLayer<?> layer = (JXLayer<?>) SwingUtilities.getAncestorOfClass(
JXLayer.class, aComponent);
if (layer != null) {
LayerUI<?> ui = ((JXLayer<?>) layer).getUI();
if (ui instanceof TransformUI) {
return layer;
}
return findJXLayer(layer);
}
return null;
}
}

76
src/main/java/org/pbjar/jxlayer/plaf/ext/transform/TransformRPMSwingX.java

@ -0,0 +1,76 @@
/*
Copyright (c) 2009, Piet Blok
All rights reserved.
<p>
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
<p>
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* Neither the name of the copyright holder nor the names of the
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
<p>
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.pbjar.jxlayer.plaf.ext.transform;
import org.jdesktop.swingx.ForwardingRepaintManager;
import org.jetbrains.annotations.NotNull;
import org.pbjar.jxlayer.repaint.RepaintManagerProvider;
import org.pbjar.jxlayer.repaint.RepaintManagerUtils;
import javax.swing.*;
/**
* A specialized {@link RepaintManager} that checks for every JComponent that is being set dirty, if
* it has a JXLayer ancestor, equipped with a TransformUI. In that case, the transformed region on
* the JXLayer is also marked dirty.
*
* <p>If this class cannot be instantiated because the SwingX packages are not on the class path,
* use {@link TransformRPMFallBack}
*
* @author Piet Blok
* @see TransformRPMFallBack
* @see RepaintManagerProvider
* @see RepaintManagerUtils
*/
@TransformRPMAnnotation
public class TransformRPMSwingX extends ForwardingRepaintManager {
/**
* Sole constructor.
*
* @param delegate the delegate {@link RepaintManager}
*/
public TransformRPMSwingX(@NotNull final RepaintManager delegate) {
super(delegate);
TransformRPMImpl.hackInitialization(delegate, this);
}
/**
* Delegates and then marks a JXLayer ancestor as dirty with the transformed rectangle.
*/
@Override
public void addDirtyRegion(@NotNull final JComponent c, final int x, final int y, final int w, final int h) {
if (!TransformRPMImpl.addDirtyRegion(c, x, y, w, h, this)) {
super.addDirtyRegion(c, x, y, w, h);
}
}
}

122
src/main/java/org/pbjar/jxlayer/plaf/ext/transform/TransformUtils.java

@ -0,0 +1,122 @@
/*
Copyright (c) 2009, Piet Blok
All rights reserved.
<p>
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
<p>
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* Neither the name of the copyright holder nor the names of the
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
<p>
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.pbjar.jxlayer.plaf.ext.transform;
import org.jdesktop.jxlayer.JXLayer;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.pbjar.jxlayer.plaf.ext.TransformUI;
import javax.swing.*;
import java.awt.*;
import java.util.Map;
/**
* Some convenience methods to create a populated transforming {@link JXLayer}.
*
* @author Piet Blok
*/
public final class TransformUtils {
@Contract(pure = true)
private TransformUtils() {
}
/**
* Create a Transform JXLayer.
*
* @param component the component.
* @return the JXLayer.
*/
@NotNull
public static JXLayer<JComponent> createTransformJXLayer(final JComponent component) {
return createTransformJXLayer(component, 1.0, null);
}
/**
* Create a Transform JXLayer.
*
* @param component the component.
* @param scale the scaling
* @param hints the rendering hints.
* @return the JXLayer.
*/
@NotNull
public static JXLayer<JComponent> createTransformJXLayer(
final JComponent component, final double scale, final Map<RenderingHints.Key, Object> hints) {
DefaultTransformModel model = new DefaultTransformModel();
model.setScale(scale);
return createTransformJXLayer(component, model, hints);
}
/**
* Create a Transform JXLayer.
*
* @param component the component.
* @param model the transform model.
* @param hints the rendering hints.
* @return the JXLayer.
*/
@Contract("_, _, _ -> new")
@NotNull
public static JXLayer<JComponent> createTransformJXLayer(
final JComponent component, final TransformModel model, final Map<RenderingHints.Key, Object> hints) {
TransformUI ui = new TransformUI(model);
ui.setRenderingHints(hints);
return new JXLayer<>(component, ui);
}
/**
* Create a Transform JXLayer.
*
* @param component the component.
* @param scale the scaling
* @return the JXLayer.
*/
@NotNull
public static JXLayer<JComponent> createTransformJXLayer(final JComponent component, final double scale) {
return createTransformJXLayer(component, scale, null);
}
/**
* Create a Transform JXLayer.
*
* @param component the component.
* @param model the transform model.
* @return the JXLayer.
*/
@NotNull
public static JXLayer<JComponent> createTransformJXLayer(
final JComponent component, final TransformModel model) {
return createTransformJXLayer(component, model, null);
}
}

76
src/main/java/org/pbjar/jxlayer/repaint/RepaintManagerProvider.java

@ -0,0 +1,76 @@
/*
Copyright (c) 2009, Piet Blok
All rights reserved.
<p>
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
<p>
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* Neither the name of the copyright holder nor the names of the
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
<p>
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.pbjar.jxlayer.repaint;
import org.jdesktop.swingx.ForwardingRepaintManager;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
/**
* To be implemented by classes that provide for a custom RepaintManager.
*
* @author Piet Blok
* @see RepaintManagerUtils
*/
public interface RepaintManagerProvider {
/**
* Get the class of a {@link RepaintManager} that extends {@link ForwardingRepaintManager}.
*
* <p><b>Note:</b> the class must provide for a public constructor that takes a delegate {@link
* RepaintManager} as its only argument.
*
* @return a class object
*/
@NotNull
Class<? extends ForwardingRepaintManager> getForwardingRepaintManagerClass();
/**
* Get the class of a {@link RepaintManager} that extends {@link WrappedRepaintManager}.
*
* <p><b>Note:</b> the class must provide for a public constructor that takes a delegate {@link
* RepaintManager} as its only argument.
*
* @return a class object
*/
@NotNull
Class<? extends WrappedRepaintManager> getWrappedRepaintManagerClass();
/**
* Checks whether or not the argument class is a {@link RepaintManager} class that will do the
* required job.
*
* @param rpm a {@link RepaintManager} class
* @return {@code true} if the argument class will do the required job, {@code false} otherwise
*/
boolean isAdequate(Class<? extends RepaintManager> rpm);
}

230
src/main/java/org/pbjar/jxlayer/repaint/RepaintManagerUtils.java

@ -0,0 +1,230 @@
/*
Copyright (c) 2009, Piet Blok
All rights reserved.
<p>
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
<p>
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* Neither the name of the copyright holder nor the names of the
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
<p>
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.pbjar.jxlayer.repaint;
import com.weis.darklaf.LogFormatter;
import org.jdesktop.swingx.ForwardingRepaintManager;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.logging.ConsoleHandler;
import java.util.logging.Logger;
/**
* Utility class that ensures that a correct {@link RepaintManager} is set.
*
* @author Piet Blok
*/
public final class RepaintManagerUtils {
private static final Logger LOGGER = Logger.getLogger(RepaintManagerUtils.class.getName());
/**
* Indicates the availability of SwingX on the class path.
*/
private static final boolean swingX = isSwingXAvailable();
static {
LOGGER.setUseParentHandlers(false);
ConsoleHandler handler = new ConsoleHandler();
handler.setFormatter(new LogFormatter());
LOGGER.addHandler(handler);
}
@Contract(pure = true)
private RepaintManagerUtils() {
}
/**
* Create and return an {@link Action} that will display the delegate structure of the current
* {@link RepaintManager}.
*
* @return an {@link Action} object
*/
@Contract(" -> new")
@NotNull
public static Action createRPDisplayAction() {
return new DisplayAction();
}
/**
* Ensure that a specific {@link RepaintManager} is set according to the requirements of the
* {@link RepaintManagerProvider}.
*
* @param c a component from which the current repaint manager can be obtained.
* @param provider the provider
*/
public static void ensureRepaintManagerSet(
final Component c, @NotNull final RepaintManagerProvider provider) {
ensureImpl(RepaintManager.currentManager(c), provider);
}
/**
* The actual implementation of ensure.
*
* @param delegate a delegate RepaintManager
* @param provider the provider that provides for the type and implementation of a delegated
* RepaintManager
*/
private static void ensureImpl(
@NotNull final RepaintManager delegate, @NotNull final RepaintManagerProvider provider) {
/*
* Setup a traversal variable.
*/
RepaintManager manager = delegate;
while (!provider.isAdequate(manager.getClass())) {
if (swingX) {
if (manager instanceof ForwardingRepaintManager) {
manager = ((ForwardingRepaintManager) manager).getDelegateManager();
} else {
RepaintManager.setCurrentManager(
createManager(provider.getForwardingRepaintManagerClass(), delegate));
break;
}
} else {
if (manager instanceof WrappedRepaintManager) {
manager = ((WrappedRepaintManager) manager).getDelegateManager();
} else {
RepaintManager.setCurrentManager(
createManager(provider.getWrappedRepaintManagerClass(), delegate));
break;
}
}
}
}
@NotNull
private static RepaintManager createManager(
@NotNull final Class<? extends RepaintManager> clazz, final RepaintManager delegate) {
try {
RepaintManager newManager = clazz.getConstructor(RepaintManager.class).newInstance(delegate);
System.out.println("Created " + newManager.getClass().getName());
return newManager;
} catch (Throwable t) {
throw new RuntimeException("Cannot instantiate " + clazz.getName(), t);
}
}
/**
* Ensure that a specific {@link RepaintManager} is set according to the requirements of the
* {@link RepaintManagerProvider}.
*
* @param c a component from which the current repaint manager can be obtained.
* @param provider the provider
*/
public static void ensureRepaintManagerSet(
final JComponent c, @NotNull final RepaintManagerProvider provider) {
ensureImpl(RepaintManager.currentManager(c), provider);
}
/**
* Detect the availability of the ForwardingRepaintManager class.
*
* @return {@code} true if available, {@code false} otherwise
*/
private static boolean isSwingXAvailable() {
try {
Class<?> clazz = ForwardingRepaintManager.class;
LOGGER.info("SwingX is available");
return clazz != null;
} catch (Throwable t) {
LOGGER.info("SwingX is not available");
return false;
}
}
private static class DisplayAction extends AbstractAction {
private static final long serialVersionUID = 1L;
public DisplayAction() {
super("RPM tree");
}
@Override
public void actionPerformed(@NotNull final ActionEvent e) {
JComponent c = (JComponent) e.getSource();
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
pw.println("The tree for the current RepaintManager:");
pw.println();
RepaintManager manager = RepaintManager.currentManager(c);
appendDelegates(pw, manager);
pw.close();
String text = sw.toString();
JTextPane message = new JTextPane();
message.setFont(Font.decode(Font.MONOSPACED));
message.setContentType("text/plain");
message.setText(text);
message.setEditable(false);
JOptionPane.showMessageDialog(
c, message, "The RepaintManager tree", JOptionPane.INFORMATION_MESSAGE);
}
private void appendClass(@NotNull final PrintWriter writer, @NotNull final Object obj) {
Class<?> clazz = obj.getClass();
String prefix = "Class: ";
while (clazz != null) {
writer.println(prefix + clazz.getName());
clazz = clazz.getSuperclass();
prefix = "Extends: ";
}
}
private void appendDelegates(@NotNull final PrintWriter writer, @NotNull final Object rp) {
appendClass(writer, rp);
@Nullable RepaintManager delegate;
if (rp instanceof WrappedRepaintManager) {
delegate = ((WrappedRepaintManager) rp).getDelegateManager();
} else if (swingX) {
if (rp instanceof ForwardingRepaintManager) {
delegate = ((ForwardingRepaintManager) rp).getDelegateManager();
} else {
delegate = null;
}
} else {
delegate = null;
}
if (delegate != null) {
writer.println();
writer.println("Delegate:");
appendDelegates(writer, delegate);
}
}
}
}

228
src/main/java/org/pbjar/jxlayer/repaint/WrappedRepaintManager.java

@ -0,0 +1,228 @@
/*
Copyright (c) 2009, Piet Blok
All rights reserved.
<p>
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
<p>
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* Neither the name of the copyright holder nor the names of the
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
<p>
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.pbjar.jxlayer.repaint;
import org.jdesktop.swingx.ForwardingRepaintManager;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.applet.Applet;
import java.awt.*;
/**
* A fall back class for when the SwingX class {@link ForwardingRepaintManager} is not available on
* the class path.
*
* <p>A {@link RepaintManager} that preserves functionality of a wrapped {@code RepaintManager}. All
* methods will delegate to the wrapped {@code RepaintManager}.
*
* <p>When sub classing this class, one must in all overridden methods call the {@code super}
* method.
*
* @author Piet Blok
* @see RepaintManagerUtils
* @see RepaintManagerProvider
* @see ForwardingRepaintManager
*/
public class WrappedRepaintManager extends RepaintManager {
/**
* The wrapped manager.
*/
@NotNull
private final RepaintManager delegate;
/**
* Construct a {@code RepaintManager} wrapping an existing {@code RepaintManager}.
*
* @param delegate an existing RepaintManager
*/
@Contract("null -> fail")
public WrappedRepaintManager(@Nullable final RepaintManager delegate) {
if (delegate == null) {
throw new NullPointerException();
}
this.delegate = delegate;
}
/**
* Just delegates. {@inheritDoc}
*/
@Override
public void addInvalidComponent(final JComponent invalidComponent) {
delegate.addInvalidComponent(invalidComponent);
}
/**
* Just delegates. {@inheritDoc}
*/
@Override
public void removeInvalidComponent(final JComponent component) {
delegate.removeInvalidComponent(component);
}
/**
* Just delegates. {@inheritDoc}
*/
@Override
public void addDirtyRegion(final JComponent c, final int x, final int y, final int w, final int h) {
delegate.addDirtyRegion(c, x, y, w, h);
}
/**
* Just delegates. {@inheritDoc}
*/
@Override
public void addDirtyRegion(final Window window, final int x, final int y, final int w, final int h) {
delegate.addDirtyRegion(window, x, y, w, h);
}
/**
* Just delegates. {@inheritDoc}
*/
@Override
@Deprecated
@SuppressWarnings("deprecation")
public void addDirtyRegion(final Applet applet, final int x, final int y, final int w, final int h) {
delegate.addDirtyRegion(applet, x, y, w, h);
}
/**
* Just delegates. {@inheritDoc}
*/
@Override
public Rectangle getDirtyRegion(final JComponent c) {
return delegate.getDirtyRegion(c);
}
/**
* Just delegates. {@inheritDoc}
*/
@Override
public Dimension getDoubleBufferMaximumSize() {
return delegate.getDoubleBufferMaximumSize();
}
/**
* Just delegates. {@inheritDoc}
*/
@Override
public void markCompletelyDirty(final JComponent c) {
delegate.markCompletelyDirty(c);
}
/**
* Just delegates. {@inheritDoc}
*/
@Override
public void setDoubleBufferMaximumSize(final Dimension d) {
delegate.setDoubleBufferMaximumSize(d);
}
/**
* Just delegates. {@inheritDoc}
*/
@Override
public void markCompletelyClean(final JComponent c) {
delegate.markCompletelyClean(c);
}
/**
* Just delegates. {@inheritDoc}
*/
@Override
public boolean isCompletelyDirty(final JComponent c) {
return delegate.isCompletelyDirty(c);
}
/**
* Just delegates. {@inheritDoc}
*/
@Override
public void validateInvalidComponents() {
delegate.validateInvalidComponents();
}
/**
* Just delegates. {@inheritDoc}
*/
@Override
public void paintDirtyRegions() {
delegate.paintDirtyRegions();
}
/**
* Just delegates. {@inheritDoc}
*/
@Override
public boolean isDoubleBufferingEnabled() {
return delegate.isDoubleBufferingEnabled();
}
/**
* Just delegates. {@inheritDoc}
*/
@Override
public Image getOffscreenBuffer(final Component c, final int proposedWidth, final int proposedHeight) {
return delegate.getOffscreenBuffer(c, proposedWidth, proposedHeight);
}
/**
* Just delegates. {@inheritDoc}
*/
@Override
public void setDoubleBufferingEnabled(final boolean flag) {
delegate.setDoubleBufferingEnabled(flag);
}
/**
* Just delegates. {@inheritDoc}
*/
@Override
public Image getVolatileOffscreenBuffer(final Component c, final int proposedWidth, final int proposedHeight) {
return delegate.getVolatileOffscreenBuffer(c, proposedWidth, proposedHeight);
}
/**
* Get the delegate.
*
* @return the delegate
*/
@Nullable
public RepaintManager getDelegateManager() {
return delegate;
}
}

2
src/main/resources/com/weis/darklaf/properties/ui/splitPane.properties

@ -22,10 +22,12 @@
# #
# suppress inspection "UnusedProperty" for whole file # suppress inspection "UnusedProperty" for whole file
SplitPaneUI = com.weis.darklaf.ui.splitpane.DarkSplitPaneUI SplitPaneUI = com.weis.darklaf.ui.splitpane.DarkSplitPaneUI
SplitPane.border = com.weis.darklaf.ui.splitpane.DarkSplitPaneBorder
SplitPaneDivider.border = com.weis.darklaf.ui.splitpane.DarkSplitPaneDividerBorder SplitPaneDivider.border = com.weis.darklaf.ui.splitpane.DarkSplitPaneDividerBorder
SplitPane.highlight = %background SplitPane.highlight = %background
SplitPane.dividerLineColor = %border SplitPane.dividerLineColor = %border
SplitPane.dividerFocusColor = null SplitPane.dividerFocusColor = null
SplitPane.centerOneTouchButtons = true
#Icons #Icons
SplitPane.horizontalGlue.icon = navigation/horizontalGlue.svg[aware](4,13) SplitPane.horizontalGlue.icon = navigation/horizontalGlue.svg[aware](4,13)

63
src/main/resources/com/weis/darklaf/properties/ui/tabFrame.properties

@ -0,0 +1,63 @@
#
# MIT License
#
# Copyright (c) 2019 Jannis Weis
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
# suppress inspection "UnusedProperty" for whole file
TabFramePanelPopupUI = com.weis.darklaf.ui.tabframe.DarkPanelPopupUI
TabFrameTabbedPopupUI = com.weis.darklaf.ui.tabframe.DarkTabbedPopupUI
TabFramePopup.headerBorder = com.weis.darklaf.ui.tabframe.DarkTabFramePopupHeaderBorder
TabFramePopup.headerBackground = %background
TabFramePopup.headerFocusBackground = %backgroundColorful
TabFramePopup.headerHoverBackground = %backgroundHover
TabFramePopup.headerSelectedBackground = %background
TabFramePopup.headerSelectedHoverBackground = %backgroundHover
TabFramePopup.headerFocusHoverBackground = %backgroundHoverColorful
TabFramePopup.headerFocusSelectedBackground = %backgroundColorful
TabFramePopup.headerFocusSelectedHoverBackground = %backgroundHoverColorful
TabFramePopup.headerButtonHoverBackground = %hoverHighlight
TabFramePopup.headerButtonClickBackground = %clickHighlight
TabFramePopup.headerButtonFocusHoverBackground = %hoverHighlightColorful
TabFramePopup.headerButtonFocusClickBackground = %clickHighlightColorful
TabFramePopup.closeAccelerator = shift pressed ESCAPE
TabFramePopup.closeTooltipText = Close (shift ESC)
TabFramePopup.minimumHeaderSize = null
TabFramePopup.borderColor = %borderSecondary
TabFrameTabLabelUI = com.weis.darklaf.ui.tabframe.DarkTabFrameTabLabelUI
TabFrameTabContainerUI = com.weis.darklaf.ui.tabframe.DarkTabFrameTabContainerUI
TabFrameTab.border = com.weis.darklaf.ui.tabframe.DarkTabFrameTabBorder
TabFrameTab.font = Dialog-0-11
TabFrameTab.foreground = %textForeground
TabFrameTab.selectedForeground = %textForegroundHighlight
TabFrameTab.selectedBackground = %backgroundSelectedSecondary
TabFrameTab.hoverBackground = %backgroundHoverSecondary
TabFrameUI = com.weis.darklaf.ui.tabframe.DarkTabFrameUI
TabFrame.line = %borderSecondary
TabFrame.tabHeight = 24
TabFrame.acceleratorKeyCode = alt pressed
#Icons
TabFramePopup.close.icon = navigation/collapse.svg[aware]

6
src/main/resources/com/weis/darklaf/properties/ui/tabbedPane.properties

@ -28,10 +28,10 @@ TabbedPane.tabRunOverlay = 0
TabbedPane.tabBorderColor = %borderSecondary TabbedPane.tabBorderColor = %borderSecondary
TabbedPane.accent = %highlightFillMono TabbedPane.accent = %highlightFillMono
TabbedPane.accentFocus = %highlightFillFocusSecondary TabbedPane.accentFocus = %highlightFillFocusSecondary
TabbedPane.selected = %backgroundSelected TabbedPane.selectedBackground = %backgroundSelected
TabbedPane.focus = %backgroundSelected TabbedPane.focus = %backgroundSelected
TabbedPane.highlight = %backgroundHover TabbedPane.hoverBackground = %backgroundHover
TabbedPane.selectHighlight = %hoverHighlightSecondary TabbedPane.selectedHoverBackground = %hoverHighlightSecondary
TabbedPane.focusBarHeight = 3 TabbedPane.focusBarHeight = 3
TabbedPane.contentBorderInsets = 0,0,0,0 TabbedPane.contentBorderInsets = 0,0,0,0

6
src/main/resources/com/weis/darklaf/theme/darcula/darcula_defaults.properties

@ -30,9 +30,10 @@ backgroundToolTip = 4b4d4d
backgroundHover = 27292a backgroundHover = 27292a
backgroundSelected = 4e5254 backgroundSelected = 4e5254
#Todo for tabFrame.
backgroundHoverSecondary = 353739 backgroundHoverSecondary = 353739
backgroundSelectedSecondary = 2d2f30 backgroundSelectedSecondary = 2d2f30
backgroundHoverColorful = 262D36
backgroundSelectedColorful = 313A45
dropBackground = 4b4b4b dropBackground = 4b4b4b
dropForeground = 999999 dropForeground = 999999
@ -48,7 +49,7 @@ gridLine = 4f5152
hoverHighlight = 4c5052 hoverHighlight = 4c5052
clickHighlight = 5c6164 clickHighlight = 5c6164
hoverHighlightColorful = 262E37 hoverHighlightColorful = 2F3A45
clickHighlightColorful = 2B353F clickHighlightColorful = 2B353F
hoverHighlightDefault = 406086 hoverHighlightDefault = 406086
@ -89,6 +90,7 @@ controlPassedFadeEnd = 5dc48f
####Text#### ####Text####
caret = bbbbbb caret = bbbbbb
textForeground = bbbbbb textForeground = bbbbbb
textForegroundHighlight = DDDDDD
textForegroundInactive = 777777 textForegroundInactive = 777777
textForegroundSecondary = 919191 textForegroundSecondary = 919191
acceleratorForeground = eeeeee acceleratorForeground = eeeeee

2
src/test/java/SplitPaneDemo.java

@ -1,5 +1,4 @@
import com.weis.darklaf.LafManager; import com.weis.darklaf.LafManager;
import com.weis.darklaf.theme.Theme;
import javax.swing.*; import javax.swing.*;
import java.awt.*; import java.awt.*;
@ -19,6 +18,7 @@ public final class SplitPaneDemo {
splitPane.setRightComponent(new JPanel() {{ splitPane.setRightComponent(new JPanel() {{
setBackground(Color.BLUE); setBackground(Color.BLUE);
}}); }});
splitPane.putClientProperty("JSplitPane.style", "invisible");
splitPane.putClientProperty("JSplitPane.style", "line"); splitPane.putClientProperty("JSplitPane.style", "line");
splitPane.setOneTouchExpandable(true); splitPane.setOneTouchExpandable(true);
frame.setContentPane(new JPanel(new BorderLayout()) {{ frame.setContentPane(new JPanel(new BorderLayout()) {{

86
src/test/java/TabFrameDemo.java

@ -0,0 +1,86 @@
import com.weis.darklaf.LafManager;
import com.weis.darklaf.components.alignment.Alignment;
import com.weis.darklaf.components.tabframe.TabFrame;
import com.weis.darklaf.components.tabframe.TabbedPopup;
import com.weis.darklaf.icons.IconLoader;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import java.awt.*;
/*
* MIT License
*
* Copyright (c) 2019 Jannis Weis
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
public class TabFrameDemo {
public static void main(final String[] args) {
SwingUtilities.invokeLater(() -> {
LafManager.install();
final JFrame frame = new JFrame();
Icon folderIcon = IconLoader.get().getUIAwareIcon("files/folder.svg", 19, 19);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
var tabFrame = new TabFrame();
for (var o : Alignment.values()) {
if (o != Alignment.CENTER) {
for (int i = 0; i < 2; i++) {
var pcc = new JPanel();
pcc.setOpaque(true);
pcc.setBackground(Color.YELLOW.darker().darker());
pcc.add(new JLabel(o.toString() + "_" + i + " Popup"));
tabFrame.addTab(pcc, o.toString() + "_" + i, folderIcon, o);
}
}
}
var tabbedPopup = new TabbedPopup("Tabbed Popup:");
tabFrame.setTabAt(tabbedPopup, "NORTH (Tabbed Pane Tab)", null, Alignment.NORTH, 0);
for (int i = 0; i < 5; i++) {
var panel = new JPanel();
var label = new JLabel("inside tab " + i);
panel.add(label);
panel.setBackground(Color.GREEN.darker().darker());
tabbedPopup.getTabbedPane().addTab("Tab " + i, panel);
}
tabFrame.setUserTabComponentAt(new JLabel("NORTH (custom tab)") {{
setBorder(new EmptyBorder(0, 5, 0, 5));
setOpaque(false);
setForeground(Color.RED);
setFont(new Font(Font.SERIF, Font.ITALIC, 12));
}}, Alignment.NORTH, 1);
tabFrame.setAcceleratorAt(1, Alignment.NORTH_WEST, 0);
var contentPane = new JPanel(new BorderLayout());
frame.setContentPane(tabFrame);
tabFrame.setContent(contentPane);
frame.pack();
frame.setSize(1000, 500);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}
}
Loading…
Cancel
Save