Java tutorial
/* * Copyright (c) 2008-2016 Haulmont. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package com.haulmont.cuba.desktop.sys.vcl; import com.haulmont.cuba.desktop.App; import org.apache.commons.compress.utils.IOUtils; import org.apache.commons.lang.StringUtils; import javax.annotation.Nullable; import javax.swing.*; import javax.swing.border.Border; import javax.swing.border.EmptyBorder; import javax.swing.border.LineBorder; import javax.swing.border.TitledBorder; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.IOException; import java.util.ArrayList; import java.util.LinkedList; /** * Panel with border and collapse/expand button */ public class CollapsiblePanel extends JPanel { protected static final int COLLAPSED_HEIGHT = 10; protected boolean expanded = true; protected boolean collapsable = false; protected JComponent composition; protected JButton titleBtn; protected boolean borderVisible = true; protected Dimension preferredSize; protected java.util.List<Runnable> postPaintActions = new LinkedList<>(); public interface CollapseListener extends java.util.EventListener { void collapsed(); void expanded(); } private java.util.List<CollapseListener> collapseListeners; private Icon expandedIcon; private Icon collapsedIcon; public CollapsiblePanel(JComponent composition) { this.composition = composition; titleBtn = new JButton(); titleBtn.setBorder(BorderFactory.createEmptyBorder(0, 3, 5, 3)); titleBtn.setVerticalTextPosition(AbstractButton.CENTER); titleBtn.setHorizontalTextPosition(AbstractButton.RIGHT); titleBtn.setMargin(new Insets(0, 0, 3, 0)); titleBtn.setFont(getTitleFont()); titleBtn.setFocusable(false); titleBtn.setContentAreaFilled(false); titleBtn.setVisible(false); titleBtn.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { if (isCollapsable()) setExpanded(!isExpanded()); } }); // Add icons loadIcons(); refreshTitleIcon(); setLayout(new BorderLayout()); add(titleBtn, BorderLayout.CENTER); add(composition, BorderLayout.CENTER); setBorder(createBorderImplementation()); preferredSize = getPreferredSize(); placeTitleComponent(); } private Font getTitleFont() { UIDefaults lafDefaults = UIManager.getLookAndFeelDefaults(); if (lafDefaults.getFont("CollapsiblePanel.font") != null) { // take it from desktop theme return lafDefaults.getFont("CollapsiblePanel.font"); } return lafDefaults.getFont("Panel.font"); } private void refreshTitleIcon() { if (collapsable) { if (expanded) { titleBtn.setIcon(expandedIcon); } else { titleBtn.setIcon(collapsedIcon); } } else { titleBtn.setIcon(null); } } private void placeTitleComponent() { Insets insets = getInsets(); Rectangle containerRectangle = getBounds(); if (borderVisible) { Rectangle componentRectangle = ((CollapsibleTitledBorder) getBorder()) .getComponentRect(containerRectangle, insets); titleBtn.setBounds(componentRectangle); } else { Dimension compD = titleBtn.getPreferredSize(); Rectangle compR = new Rectangle(20 - insets.left, 0, compD.width, compD.height); titleBtn.setBounds(compR); } } @Override public Dimension getPreferredSize() { int preferredHeight = super.getPreferredSize().height; int preferredWidth = titleBtn.getPreferredSize().width + titleBtn.getBounds().x * 2; if (preferredWidth > super.getPreferredSize().width) { return new Dimension(preferredWidth, preferredHeight); } return super.getPreferredSize(); } protected void loadIcons() { App app = App.getInstance(); if (app != null) { expandedIcon = app.getResources().getIcon("components/groupbox/item-expanded.png"); collapsedIcon = app.getResources().getIcon("components/groupbox/item-collapsed.png"); } else { // fallback for unit tests expandedIcon = readIcon("components/groupbox/item-expanded.png"); collapsedIcon = readIcon("components/groupbox/item-collapsed.png"); } } protected ImageIcon readIcon(String iconPath) { try { String iconResourcePath = "/com/haulmont/cuba/desktop/res/nimbus/" + iconPath; return new ImageIcon(IOUtils.toByteArray(getClass().getResourceAsStream(iconResourcePath))); } catch (IOException e) { throw new RuntimeException("Unable to read icon", e); } } protected void expandPanel() { composition.setVisible(true); postPaintActions.add(new Runnable() { @Override public void run() { titleBtn.setIcon(expandedIcon); setPreferredSize(preferredSize); updateUI(); } }); fireExpandListeners(); repaint(); } private void collapsePanel() { composition.setVisible(false); postPaintActions.add(new Runnable() { @Override public void run() { int collapsedWidth = getWidth(); int collapsedHeight = COLLAPSED_HEIGHT; preferredSize = getPreferredSize(); titleBtn.setIcon(collapsedIcon); setPreferredSize(new Dimension(collapsedWidth, collapsedHeight)); updateUI(); } }); fireCollapseListeners(); repaint(); } @Override public void paint(Graphics g) { super.paint(g); SwingUtilities.invokeLater(new Runnable() { @Override public void run() { for (Runnable action : postPaintActions) { action.run(); } postPaintActions.clear(); } }); } public boolean isExpanded() { return !collapsable || expanded; } public void setExpanded(boolean expanded) { if (this.expanded != expanded) { this.expanded = expanded; if (collapsable) { if (expanded) { expandPanel(); } else { collapsePanel(); } } } } public boolean isCollapsable() { return collapsable; } public void setCollapsible(boolean collapsable) { if (this.collapsable != collapsable) { if (!collapsable && !expanded) { setExpanded(true); } this.collapsable = collapsable; revalidate(); repaint(); postPaintActions.add(new Runnable() { @Override public void run() { refreshTitleIcon(); placeTitleComponent(); titleBtn.validate(); titleBtn.repaint(); } }); } } public boolean isBorderVisible() { return borderVisible; } public void setBorderVisible(boolean borderVisible) { this.borderVisible = borderVisible; if (borderVisible) { setBorder(createBorderImplementation()); placeTitleComponent(); } else { if (titleBtn.isVisible()) { this.setBorder(new EmptyBorder(12, 2, 2, 2)); } else { this.setBorder(new EmptyBorder(0, 0, 0, 0)); } placeTitleComponent(); } this.repaint(); } private CollapsibleTitledBorder createBorderImplementation() { Border border = LineBorder.createGrayLineBorder(); return new CollapsibleTitledBorder(border, titleBtn); } public JComponent getComposition() { return composition; } public void setComposition(JComponent composition) { this.composition = composition; updateUI(); placeTitleComponent(); } public String getCaption() { return titleBtn.getText(); } public void setCaption(String caption) { if (StringUtils.isEmpty(caption)) titleBtn.setVisible(false); else titleBtn.setVisible(true); titleBtn.setText(caption); setBorderVisible(borderVisible); } public void addCollapseListener(CollapseListener collapseListener) { if (collapseListeners == null) collapseListeners = new ArrayList<>(); collapseListeners.add(collapseListener); } public void removeCollapseListener(CollapseListener collapseListener) { if (collapseListeners != null) { collapseListeners.remove(collapseListener); if (collapseListeners.isEmpty()) collapseListeners = null; } } protected void fireExpandListeners() { if (collapseListeners != null) { for (final CollapsiblePanel.CollapseListener collapseListener : collapseListeners) { collapseListener.expanded(); } } } protected void fireCollapseListeners() { if (collapseListeners != null) { for (final CollapsiblePanel.CollapseListener collapseListener : collapseListeners) { collapseListener.collapsed(); } } } protected class CollapsibleTitledBorder extends TitledBorder { private JButton titleComponent; public CollapsibleTitledBorder(Border border) { this(border, null, LEFT, TOP); } public CollapsibleTitledBorder(@Nullable Border border, JButton titleButton) { this(border, titleButton, LEFT, TOP); } public CollapsibleTitledBorder(@Nullable Border border, @Nullable JButton titleButton, int titleJustification, int titlePosition) { super(border, null, titleJustification, titlePosition, null, null); this.titleComponent = titleButton; if (border == null) { this.border = super.getBorder(); } } @Override public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) { if (!borderVisible) return; if (StringUtils.isEmpty(getCaption())) { super.paintBorder(c, g, x, y, width, height); return; } Rectangle borderR = new Rectangle(x + EDGE_SPACING, y + EDGE_SPACING, width - (EDGE_SPACING * 2), height - (EDGE_SPACING * 2)); Insets borderInsets = new Insets(3, 3, 3, 3); Rectangle rect = new Rectangle(x, y, width, height); Insets insets = getBorderInsets(c); Rectangle compR = getComponentRect(rect, insets); int diff; switch (titlePosition) { case ABOVE_TOP: diff = compR.height + TEXT_SPACING; borderR.y += diff; borderR.height -= diff; break; case TOP: case DEFAULT_POSITION: diff = insets.top / 2 - borderInsets.top - EDGE_SPACING; borderR.y += diff; borderR.height -= diff; break; case BELOW_TOP: case ABOVE_BOTTOM: break; case BOTTOM: diff = insets.bottom / 2 - borderInsets.bottom - EDGE_SPACING; borderR.height -= diff; break; case BELOW_BOTTOM: diff = compR.height + TEXT_SPACING; borderR.height -= diff; break; } border.paintBorder(c, g, borderR.x, borderR.y, borderR.width, borderR.height); Color col = g.getColor(); g.setColor(c.getBackground()); g.fillRect(compR.x, compR.y, compR.width, compR.height); g.setColor(col); } @Override public Insets getBorderInsets(Component c, Insets insets) { Insets borderInsets = (Insets) UIManager.getLookAndFeelDefaults().get("CollapsiblePanel.borderInsets"); if (borderInsets == null) { borderInsets = new Insets(10, 3, 3, 3); } insets.top = EDGE_SPACING + TEXT_SPACING + borderInsets.top; insets.right = EDGE_SPACING + TEXT_SPACING + borderInsets.right; insets.bottom = EDGE_SPACING + TEXT_SPACING + borderInsets.bottom; insets.left = EDGE_SPACING + TEXT_SPACING + borderInsets.left; if (c == null || titleComponent == null) { return insets; } int compHeight = titleComponent.getPreferredSize().height; switch (titlePosition) { case ABOVE_TOP: insets.top += compHeight + TEXT_SPACING; break; case TOP: case DEFAULT_POSITION: insets.top += (compHeight / 2 - TEXT_SPACING); break; case BELOW_TOP: insets.top += compHeight + TEXT_SPACING; break; case ABOVE_BOTTOM: insets.bottom += compHeight + TEXT_SPACING; break; case BOTTOM: insets.bottom += Math.max(compHeight, borderInsets.bottom) - borderInsets.bottom; break; case BELOW_BOTTOM: insets.bottom += compHeight + TEXT_SPACING; break; } return insets; } public Rectangle getComponentRect(Rectangle rect, Insets borderInsets) { Dimension compD = titleComponent.getPreferredSize(); Rectangle compR = new Rectangle(0, 0, compD.width, compD.height); switch (titlePosition) { case ABOVE_TOP: compR.y = EDGE_SPACING; break; case TOP: case DEFAULT_POSITION: compR.y = EDGE_SPACING + (borderInsets.top - EDGE_SPACING - TEXT_SPACING * 0 - compD.height) / 2; break; case BELOW_TOP: compR.y = borderInsets.top - compD.height - TEXT_SPACING; break; case ABOVE_BOTTOM: compR.y = rect.height - borderInsets.bottom + TEXT_SPACING; break; case BOTTOM: compR.y = rect.height - borderInsets.bottom + TEXT_SPACING + (borderInsets.bottom - EDGE_SPACING - TEXT_SPACING - compD.height) / 2; break; case BELOW_BOTTOM: compR.y = rect.height - compD.height - EDGE_SPACING; break; } switch (titleJustification) { case LEFT: case DEFAULT_JUSTIFICATION: compR.x = TEXT_INSET_H + borderInsets.left - EDGE_SPACING; break; case RIGHT: compR.x = rect.width - borderInsets.right - TEXT_INSET_H - compR.width; break; case CENTER: compR.x = (rect.width - compR.width) / 2; break; } return compR; } } }