From a3587d02db1dc57c71e7fa317b615f64ba87de80 Mon Sep 17 00:00:00 2001 From: weisj Date: Wed, 1 Apr 2020 11:44:25 +0200 Subject: [PATCH] Improved rendering for popupmenus that are too large for the screen. --- .../darklaf/components/ScrollPopupMenu.java | 125 +++------------- .../darklaf/ui/combobox/DarkComboPopup.java | 4 +- .../ui/popupmenu/DarkPopupMenuBorder.java | 1 - .../darklaf/ui/popupmenu/DarkPopupMenuUI.java | 37 ++++- .../ui/popupmenu/PopupMenuContainer.java | 139 ++++++++++++++++++ .../ui/scrollpane/DarkScrollBarUI.java | 4 +- .../weisj/darklaf/ui/tooltip/ToolTipUtil.java | 32 +--- .../github/weisj/darklaf/util/DarkUIUtil.java | 41 ++++++ 8 files changed, 241 insertions(+), 142 deletions(-) create mode 100644 core/src/main/java/com/github/weisj/darklaf/ui/popupmenu/PopupMenuContainer.java diff --git a/core/src/main/java/com/github/weisj/darklaf/components/ScrollPopupMenu.java b/core/src/main/java/com/github/weisj/darklaf/components/ScrollPopupMenu.java index d71cd776..12a1cb17 100644 --- a/core/src/main/java/com/github/weisj/darklaf/components/ScrollPopupMenu.java +++ b/core/src/main/java/com/github/weisj/darklaf/components/ScrollPopupMenu.java @@ -23,12 +23,10 @@ */ package com.github.weisj.darklaf.components; -import com.github.weisj.darklaf.util.DarkUIUtil; +import com.github.weisj.darklaf.ui.popupmenu.PopupMenuContainer; import com.github.weisj.darklaf.util.PropertyKey; import javax.swing.*; -import javax.swing.event.MenuKeyEvent; -import javax.swing.event.MenuKeyListener; import java.awt.*; /** @@ -36,60 +34,16 @@ import java.awt.*; */ public class ScrollPopupMenu extends JPopupMenu { - private final JPanel contentPane; - private final JScrollPane scrollPane; + private final PopupMenuContainer popupMenuContainer; private int maxHeight; - private JWindow popWin; + private Popup popup; private int posX; private int posY; - private JPanel view; + private boolean isVisible; public ScrollPopupMenu(final int maxHeight) { + popupMenuContainer = new PopupMenuContainer(); this.maxHeight = maxHeight; - contentPane = new JPanel(new BorderLayout()); - OverlayScrollPane overlayScrollPane = createScrollPane(); - scrollPane = overlayScrollPane.getScrollPane(); - contentPane.add(overlayScrollPane, BorderLayout.CENTER); - contentPane.setBorder(getBorder()); - setDoubleBuffered(true); - MenuKeyListener menuKeyListener = new MenuKeyListener() { - @Override - public void menuKeyTyped(final MenuKeyEvent e) { - } - - @Override - public void menuKeyPressed(final MenuKeyEvent e) { - SwingUtilities.invokeLater(() -> { - MenuElement[] path = e.getMenuSelectionManager().getSelectedPath(); - if (path.length == 0) { - return; - } - Rectangle bounds = path[path.length - 1].getComponent().getBounds(); - Rectangle r = SwingUtilities.convertRectangle(ScrollPopupMenu.this, bounds, scrollPane); - scrollPane.getViewport().scrollRectToVisible(r); - }); - } - - @Override - public void menuKeyReleased(final MenuKeyEvent e) { - - } - }; - addMenuKeyListener(menuKeyListener); - } - - - private OverlayScrollPane createScrollPane() { - view = new JPanel(new BorderLayout()); - view.add(this, BorderLayout.CENTER); - OverlayScrollPane overlayScrollPane = - new OverlayScrollPane(view, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, - JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); - JScrollBar bar = overlayScrollPane.getVerticalScrollBar(); - bar.putClientProperty("JScrollBar.thin", Boolean.TRUE); - DarkUIUtil.doNotCancelPopupSetup(bar); - DarkUIUtil.doNotCancelPopupSetup(overlayScrollPane.getScrollPane()); - return overlayScrollPane; } /** @@ -104,27 +58,10 @@ public class ScrollPopupMenu extends JPopupMenu { this.maxHeight = maxHeight; } - @Override - public JMenuItem add(final JMenuItem menuItem) { - menuItem.getModel().addChangeListener(e -> contentPane.repaint(menuItem.getBounds())); - return super.add(menuItem); - } - protected void showPopup() { - Component comp = getInvoker(); - if (comp == null) return; - - while (comp.getParent() != null) { - comp = comp.getParent(); - } - - if (popWin == null || popWin.getOwner() != comp) { - popWin = comp instanceof Window ? new JWindow((Window) comp) : new JWindow(new JFrame()); - } - pack(); - popWin.setLocation(posX, posY); - popWin.setVisible(true); - requestFocus(); + isVisible = true; + popup = createPopup(); + popup.show(); } /** @@ -132,24 +69,19 @@ public class ScrollPopupMenu extends JPopupMenu { * * @return scroll pane; */ - public JScrollPane getScrollPane() { - return scrollPane; + return popupMenuContainer.getScrollPane(); } @Override public boolean isVisible() { - return popWin != null && popWin.isShowing(); + return isVisible; } @Override public void setLocation(final int x, final int y) { - if (popWin != null && popWin.isShowing()) { - popWin.setLocation(x, y); - } else { - posX = x; - posY = y; - } + posX = x; + posY = y; } @@ -177,10 +109,11 @@ public class ScrollPopupMenu extends JPopupMenu { } protected void hidePopup() { - if (popWin != null) { + if (popup != null) { firePopupMenuWillBecomeInvisible(); - popWin.setVisible(false); - popWin = null; + popup.hide(); + isVisible = false; + popup = null; firePropertyChange(PropertyKey.VISIBLE, Boolean.TRUE, Boolean.FALSE); if (isPopupMenu()) { MenuSelectionManager.defaultManager().clearSelectedPath(); @@ -188,30 +121,12 @@ public class ScrollPopupMenu extends JPopupMenu { } } + private Popup createPopup() { + return popupMenuContainer.createPopup(this, posX, posY, maxHeight); + } + @Override public void pack() { - if (popWin == null) { - return; - } - final Dimension prefSize = getPreferredSize(); - if (maxHeight <= 0 || prefSize.height <= maxHeight) { - setBounds(0, 0, prefSize.width, prefSize.height); - popWin.setContentPane(this); - setBorderPainted(true); - popWin.setSize(prefSize.width, prefSize.height); - } else { - int increment = getComponentCount() > 0 - ? Math.max(1, getComponent(0).getPreferredSize().height / 2) - : 1; - JScrollBar bar = scrollPane.getVerticalScrollBar(); - bar.setValue(bar.getMinimum()); - bar.setUnitIncrement(increment); - setBorderPainted(false); - view.add(this); - popWin.setContentPane(contentPane); - popWin.pack(); - popWin.setSize(prefSize.width + bar.getPreferredSize().width, maxHeight); - } } private boolean isPopupMenu() { diff --git a/core/src/main/java/com/github/weisj/darklaf/ui/combobox/DarkComboPopup.java b/core/src/main/java/com/github/weisj/darklaf/ui/combobox/DarkComboPopup.java index 6d1acc26..d864a8f3 100644 --- a/core/src/main/java/com/github/weisj/darklaf/ui/combobox/DarkComboPopup.java +++ b/core/src/main/java/com/github/weisj/darklaf/ui/combobox/DarkComboPopup.java @@ -24,6 +24,7 @@ package com.github.weisj.darklaf.ui.combobox; import com.github.weisj.darklaf.components.OverlayScrollPane; +import com.github.weisj.darklaf.ui.scrollpane.DarkScrollBarUI; import javax.swing.*; import javax.swing.plaf.basic.BasicComboPopup; @@ -93,7 +94,7 @@ public class DarkComboPopup extends BasicComboPopup { protected JScrollPane createScroller() { overlayScrollPane = new OverlayScrollPane(list, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); - overlayScrollPane.getVerticalScrollBar().putClientProperty("JScrollBar.thin", Boolean.TRUE); + overlayScrollPane.getVerticalScrollBar().putClientProperty(DarkScrollBarUI.KEY_SMALL, Boolean.TRUE); return overlayScrollPane.getScrollPane(); } @@ -110,7 +111,6 @@ public class DarkComboPopup extends BasicComboPopup { setBorderPainted(true); setOpaque(false); add(overlayScrollPane); - setDoubleBuffered(true); setFocusable(false); } diff --git a/core/src/main/java/com/github/weisj/darklaf/ui/popupmenu/DarkPopupMenuBorder.java b/core/src/main/java/com/github/weisj/darklaf/ui/popupmenu/DarkPopupMenuBorder.java index d5d8db9a..904967fe 100644 --- a/core/src/main/java/com/github/weisj/darklaf/ui/popupmenu/DarkPopupMenuBorder.java +++ b/core/src/main/java/com/github/weisj/darklaf/ui/popupmenu/DarkPopupMenuBorder.java @@ -42,7 +42,6 @@ public class DarkPopupMenuBorder extends MutableLineBorder implements UIResource @Override public void paintBorder(final Component c, final Graphics g, final int x, final int y, final int width, final int height) { - setColor(UIManager.getDefaults().getColor("PopupMenu.borderColor")); super.paintBorder(c, g, x, y, width, height); } diff --git a/core/src/main/java/com/github/weisj/darklaf/ui/popupmenu/DarkPopupMenuUI.java b/core/src/main/java/com/github/weisj/darklaf/ui/popupmenu/DarkPopupMenuUI.java index cf43fa2c..41cbd438 100644 --- a/core/src/main/java/com/github/weisj/darklaf/ui/popupmenu/DarkPopupMenuUI.java +++ b/core/src/main/java/com/github/weisj/darklaf/ui/popupmenu/DarkPopupMenuUI.java @@ -23,7 +23,10 @@ */ package com.github.weisj.darklaf.ui.popupmenu; +import com.github.weisj.darklaf.components.OverlayScrollPane; +import com.github.weisj.darklaf.components.ScrollPopupMenu; import com.github.weisj.darklaf.ui.DarkPopupFactory; +import com.github.weisj.darklaf.util.DarkUIUtil; import sun.awt.SunToolkit; import javax.swing.*; @@ -54,6 +57,7 @@ public class DarkPopupMenuUI extends BasicPopupMenuUI { "doNotCancelPopup")); public static final String KEY_DEFAULT_LIGHTWEIGHT_POPUPS = "PopupMenu.defaultLightWeightPopups"; protected static MouseGrabber mouseGrabber; + private PopupMenuContainer popupMenuContainer; public static ComponentUI createUI(final JComponent x) { return new DarkPopupMenuUI(); @@ -89,6 +93,9 @@ public class DarkPopupMenuUI extends BasicPopupMenuUI { @Override public void installDefaults() { + if (!(popupMenu instanceof ScrollPopupMenu)) { + popupMenuContainer = new PopupMenuContainer(); + } super.installDefaults(); popupMenu.putClientProperty(DarkPopupFactory.KEY_START_HIDDEN, true); } @@ -102,6 +109,13 @@ public class DarkPopupMenuUI extends BasicPopupMenuUI { } } + @Override + public Popup getPopup(final JPopupMenu popup, final int x, final int y) { + if (popupMenuContainer == null) return super.getPopup(popup, x, y); + int maxHeight = DarkUIUtil.getScreenBounds(popup, x, y, false).height; + return popupMenuContainer.createPopup(popup, x, y, maxHeight); + } + /** * This Method is responsible for removing the old MouseGrabber from the AppContext, to be able to add our own * implementation for it that is a bit more generous with closing the popup. @@ -110,7 +124,12 @@ public class DarkPopupMenuUI extends BasicPopupMenuUI { MenuSelectionManager menuSelectionManager = MenuSelectionManager.defaultManager(); ChangeListener mouseGrabber = null; for (ChangeListener listener : menuSelectionManager.getChangeListeners()) { - if (listener.getClass().getEnclosingClass().getName().endsWith("BasicPopupMenuUI")) { + if (listener == null) continue; + Class listenerClass = listener.getClass(); + if (listenerClass == null) continue; + Class enclosingClass = listenerClass.getEnclosingClass(); + if (enclosingClass == null) continue; + if (enclosingClass.getName().endsWith("BasicPopupMenuUI")) { mouseGrabber = listener; break; } @@ -211,6 +230,22 @@ public class DarkPopupMenuUI extends BasicPopupMenuUI { } lastPathSelected = p; + repaintIfNecessary(e); + } + + protected void repaintIfNecessary(final ChangeEvent e) { + Object source = e.getSource(); + if (source instanceof MenuSelectionManager) { + MenuSelectionManager manager = (MenuSelectionManager) source; + MenuElement[] path = manager.getSelectedPath(); + if (path != null && path.length > 0) { + Component comp = path[0].getComponent(); + if (comp.isVisible()) { + OverlayScrollPane sp = DarkUIUtil.getParentOfType(OverlayScrollPane.class, comp); + if (sp != null) sp.repaint(comp.getBounds()); + } + } + } } public void eventDispatched(final AWTEvent ev) { diff --git a/core/src/main/java/com/github/weisj/darklaf/ui/popupmenu/PopupMenuContainer.java b/core/src/main/java/com/github/weisj/darklaf/ui/popupmenu/PopupMenuContainer.java new file mode 100644 index 00000000..8ec74c72 --- /dev/null +++ b/core/src/main/java/com/github/weisj/darklaf/ui/popupmenu/PopupMenuContainer.java @@ -0,0 +1,139 @@ +/* + * MIT License + * + * Copyright (c) 2020 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.github.weisj.darklaf.ui.popupmenu; + +import com.github.weisj.darklaf.components.OverlayScrollPane; +import com.github.weisj.darklaf.decorators.PopupMenuAdapter; +import com.github.weisj.darklaf.ui.scrollpane.DarkScrollBarUI; +import com.github.weisj.darklaf.util.DarkUIUtil; + +import javax.swing.*; +import javax.swing.event.MenuKeyEvent; +import javax.swing.event.MenuKeyListener; +import javax.swing.event.PopupMenuEvent; +import javax.swing.event.PopupMenuListener; +import java.awt.*; + +public class PopupMenuContainer extends JPanel { + + private final JScrollPane scrollPane; + private final MenuKeyListener menuKeyListener; + private final PopupMenuListener menuListener; + private JPanel view; + private JPopupMenu popupMenu; + + public PopupMenuContainer() { + super(new BorderLayout()); + view = new JPanel(new BorderLayout()); + OverlayScrollPane overlayScrollPane = createScrollPane(view); + scrollPane = overlayScrollPane.getScrollPane(); + add(overlayScrollPane, BorderLayout.CENTER); + menuKeyListener = new MenuKeyListener() { + @Override + public void menuKeyTyped(final MenuKeyEvent e) { + } + + @Override + public void menuKeyPressed(final MenuKeyEvent e) { + SwingUtilities.invokeLater(() -> { + if (popupMenu == null) return; + MenuElement[] path = e.getMenuSelectionManager().getSelectedPath(); + if (path.length == 0) { + return; + } + Rectangle bounds = path[path.length - 1].getComponent().getBounds(); + Rectangle r = SwingUtilities.convertRectangle(popupMenu, bounds, scrollPane); + scrollPane.getViewport().scrollRectToVisible(r); + }); + } + + @Override + public void menuKeyReleased(final MenuKeyEvent e) { + } + }; + menuListener = new PopupMenuAdapter() { + @Override + public void popupMenuWillBecomeInvisible(final PopupMenuEvent e) { + onHide(); + } + + @Override + public void popupMenuCanceled(final PopupMenuEvent e) { + onHide(); + } + + private void onHide() { + if (popupMenu == null) return; + popupMenu.removePopupMenuListener(this); + popupMenu.removeMenuKeyListener(menuKeyListener); + } + }; + } + + public void setPopupMenu(final JPopupMenu popupMenu) { + this.popupMenu = popupMenu; + popupMenu.removeMenuKeyListener(menuKeyListener); + popupMenu.removePopupMenuListener(menuListener); + popupMenu.addMenuKeyListener(menuKeyListener); + popupMenu.addPopupMenuListener(menuListener); + } + + public Popup createPopup(final JPopupMenu popupMenu, final int posX, final int posY, final int maxHeight) { + setPopupMenu(popupMenu); + final Dimension prefSize = popupMenu.getPreferredSize(); + if (maxHeight <= 0 || prefSize.height <= maxHeight) { + setBounds(0, 0, prefSize.width, prefSize.height); + popupMenu.setBorderPainted(true); + return PopupFactory.getSharedInstance().getPopup(popupMenu.getInvoker(), popupMenu, posX, posY); + } else { + int increment = popupMenu.getComponentCount() > 0 + ? Math.max(1, popupMenu.getComponent(0).getPreferredSize().height / 2) + : 1; + JScrollBar bar = scrollPane.getVerticalScrollBar(); + bar.setValue(bar.getMinimum()); + bar.setUnitIncrement(increment); + view.removeAll(); + view.add(popupMenu, BorderLayout.CENTER); + setBorder(popupMenu.getBorder()); + popupMenu.setBorderPainted(false); + setPreferredSize(new Dimension(prefSize.width + bar.getPreferredSize().width, maxHeight)); + return PopupFactory.getSharedInstance().getPopup(popupMenu.getInvoker(), this, posX, posY); + } + } + + private OverlayScrollPane createScrollPane(final JComponent content) { + OverlayScrollPane overlayScrollPane = + new OverlayScrollPane(content, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, + JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); + JScrollBar bar = overlayScrollPane.getVerticalScrollBar(); + bar.putClientProperty(DarkScrollBarUI.KEY_SMALL, Boolean.TRUE); + DarkUIUtil.doNotCancelPopupSetup(bar); + DarkUIUtil.doNotCancelPopupSetup(overlayScrollPane.getScrollPane()); + return overlayScrollPane; + } + + public JScrollPane getScrollPane() { + return scrollPane; + } +} diff --git a/core/src/main/java/com/github/weisj/darklaf/ui/scrollpane/DarkScrollBarUI.java b/core/src/main/java/com/github/weisj/darklaf/ui/scrollpane/DarkScrollBarUI.java index b2c687d7..f7e76c1c 100644 --- a/core/src/main/java/com/github/weisj/darklaf/ui/scrollpane/DarkScrollBarUI.java +++ b/core/src/main/java/com/github/weisj/darklaf/ui/scrollpane/DarkScrollBarUI.java @@ -73,8 +73,8 @@ public class DarkScrollBarUI extends BasicScrollBarUI implements ScrollBarConsta thumbFadeStartColor = UIManager.getColor("ScrollBar.fadeStartColor"); thumbFadeEndColor = UIManager.getColor("ScrollBar.fadeEndColor"); trackBackground = UIManager.getColor("ScrollBar.trackColor"); - smallSize = UIManager.getInt("ScrollBar.width"); - size = UIManager.getInt("ScrollBar.smallWidth"); + smallSize = UIManager.getInt("ScrollBar.smallWidth"); + size = UIManager.getInt("ScrollBar.width"); } @Override diff --git a/core/src/main/java/com/github/weisj/darklaf/ui/tooltip/ToolTipUtil.java b/core/src/main/java/com/github/weisj/darklaf/ui/tooltip/ToolTipUtil.java index d80eb6b3..2d20846c 100644 --- a/core/src/main/java/com/github/weisj/darklaf/ui/tooltip/ToolTipUtil.java +++ b/core/src/main/java/com/github/weisj/darklaf/ui/tooltip/ToolTipUtil.java @@ -62,7 +62,7 @@ public class ToolTipUtil { if (!context.isBestFit()) { return context.getToolTipLocation(p, null); } - Rectangle screenBounds = getScreenBounds(context.getTarget(), p); + Rectangle screenBounds = DarkUIUtil.getScreenBounds(context.getTarget(), p); Rectangle windowBounds = DarkUIUtil.getWindow(context.getTarget()).getBounds(); Rectangle tooltipBounds = new Rectangle(); tooltipBounds.setSize(context.getToolTip().getPreferredSize()); @@ -161,34 +161,4 @@ public class ToolTipUtil { Point targetPos = target.getLocationOnScreen(); window.setLocation(targetPos.x + x, targetPos.y + y); } - - protected static Rectangle getScreenBounds(final JComponent target, final Point p) { - GraphicsConfiguration gc = getDrawingGC(p); - if (gc == null) { - gc = target.getGraphicsConfiguration(); - } - - Rectangle sBounds = gc.getBounds(); - Insets screenInsets = Toolkit.getDefaultToolkit() - .getScreenInsets(gc); - // Take into account screen insets, decrease viewport - sBounds.x += screenInsets.left; - sBounds.y += screenInsets.top; - sBounds.width -= (screenInsets.left + screenInsets.right); - sBounds.height -= (screenInsets.top + screenInsets.bottom); - return sBounds; - } - - private static GraphicsConfiguration getDrawingGC(final Point location) { - GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment(); - GraphicsDevice[] devices = env.getScreenDevices(); - for (GraphicsDevice device : devices) { - GraphicsConfiguration config = device.getDefaultConfiguration(); - Rectangle rect = config.getBounds(); - if (rect.contains(location)) { - return config; - } - } - return null; - } } diff --git a/core/src/main/java/com/github/weisj/darklaf/util/DarkUIUtil.java b/core/src/main/java/com/github/weisj/darklaf/util/DarkUIUtil.java index 501ef210..e34f8c99 100644 --- a/core/src/main/java/com/github/weisj/darklaf/util/DarkUIUtil.java +++ b/core/src/main/java/com/github/weisj/darklaf/util/DarkUIUtil.java @@ -464,6 +464,47 @@ public final class DarkUIUtil { return p; } + public static Rectangle getScreenBounds(final JComponent target, final Point p) { + return getScreenBounds(target, p.x, p.y); + } + + public static Rectangle getScreenBounds(final JComponent target, final int x, final int y) { + return getScreenBounds(target, x, y, true); + } + + public static Rectangle getScreenBounds(final JComponent target, final int x, final int y, + final boolean subtractInsets) { + GraphicsConfiguration gc = getDrawingGC(x, y); + if (gc == null) { + gc = target.getGraphicsConfiguration(); + } + + Rectangle sBounds = gc.getBounds(); + if (subtractInsets) { + // Take into account screen insets, decrease viewport + Insets screenInsets = Toolkit.getDefaultToolkit() + .getScreenInsets(gc); + sBounds.x += screenInsets.left; + sBounds.y += screenInsets.top; + sBounds.width -= (screenInsets.left + screenInsets.right); + sBounds.height -= (screenInsets.top + screenInsets.bottom); + } + return sBounds; + } + + private static GraphicsConfiguration getDrawingGC(final int x, final int y) { + GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment(); + GraphicsDevice[] devices = env.getScreenDevices(); + for (GraphicsDevice device : devices) { + GraphicsConfiguration config = device.getDefaultConfiguration(); + Rectangle rect = config.getBounds(); + if (rect.contains(x, y)) { + return config; + } + } + return null; + } + public enum Outline { error { @Override