/** * Copyright (C) 2001-2017 by RapidMiner and the contributors * * Complete list of developers available at our web site: * * http://rapidminer.com * * This program is free software: you can redistribute it and/or modify it under the terms of the * GNU Affero General Public License as published by the Free Software Foundation, either version 3 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License along with this program. * If not, see http://www.gnu.org/licenses/. */ package com.rapidminer.gui.tools.components; import java.awt.CardLayout; import java.awt.Cursor; import java.awt.Dimension; import java.awt.Font; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.GraphicsConfiguration; import java.awt.Insets; import java.awt.RenderingHints; import java.awt.event.ActionEvent; import java.awt.event.FocusAdapter; import java.awt.event.FocusEvent; import java.awt.event.FocusListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.geom.GeneralPath; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.JButton; import javax.swing.JMenuItem; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JToolBar; import javax.swing.SwingConstants; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.event.PopupMenuEvent; import javax.swing.event.PopupMenuListener; import org.jdesktop.swingx.border.DropShadowBorder; import com.rapidminer.gui.ApplicationFrame; import com.rapidminer.gui.tools.ArrowButton; import com.rapidminer.gui.tools.ViewToolBar; import com.vlsolutions.swing.toolbars.VLToolBar; /** * This is basically a {@link FancyDropDownButton} which looks fancier. Sadly the * {@link DropDownButton} is not very override friendly in terms of painting, so this class was * added which basically duplicates functionality from {@link DropDownButton}. * * @author Tobias Malbrecht, Marco Boeck */ public abstract class FancyDropDownButton extends JButton implements FancyConstants { private static final long serialVersionUID = 7512959784153390965L; private final class DefaultArrowAction extends AbstractAction { private static final long serialVersionUID = 1L; @Override public void actionPerformed(ActionEvent ae) { JPopupMenu popup = getPopupMenu(); popup.addPopupMenuListener(popupMenuListener); int popupPrefHeight = (int) popup.getPreferredSize().getHeight(); int buttonY = mainButton.getLocationOnScreen().y; boolean showOnTop = false; GraphicsConfiguration graphicsConf = ApplicationFrame.getApplicationFrame().getGraphicsConfiguration(); if (graphicsConf != null) { int windowHeight = (int) graphicsConf.getBounds().getHeight(); showOnTop = buttonY + mainButton.getHeight() + popupPrefHeight > windowHeight; } if (showOnTop) { popup.show(mainButton, 0, -popupPrefHeight); } else { popup.show(mainButton, 0, mainButton.getHeight()); } } } public static class DropDownArrowButton extends ArrowButton { private static final long serialVersionUID = -398619111521186260L; private float sizeFactor = 1; private JButton mainButton; public DropDownArrowButton(JButton mainButton) { super(SwingConstants.SOUTH); this.mainButton = mainButton; } @Override public void paintComponent(Graphics g) { if (mainButton != null && mainButton.getModel().isArmed() || getModel().isArmed()) { ((Graphics2D) g).translate(1.1, 1.1); } ((Graphics2D) g).setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); super.paintComponent(g); Graphics2D g2 = (Graphics2D) g.create(); // fill background white g2.setColor(BACKGROUND_COLOR); g2.fillRect(3, 0, getWidth() - 9, getHeight() - 6); // draw border which complements the DropShadow g2.setColor(BORDER_LIGHTEST_GRAY); g2.drawLine(3, 0, 3, getHeight() - 6); g2.drawLine(getWidth() - 6, 0, getWidth() - 6, getHeight() - 6); g2.drawLine(3, getHeight() - 6, getWidth() - 6, getHeight() - 6); g2.drawLine(3, 0, getWidth() - 6, 0); // draw arrow GeneralPath arrow = new GeneralPath(); int w, h; h = (int) (2 * sizeFactor); w = (int) (4 * sizeFactor); arrow.moveTo(getWidth() / 2 - w, getHeight() / 2); arrow.lineTo(getWidth() / 2 + w, getHeight() / 2); arrow.lineTo(getWidth() / 2, getHeight() / 2 + 2 * h); arrow.closePath(); if (isEnabled()) { g2.setColor(HOVERED_TEXTCOLOR); } else { g2.setColor(BORDER_LIGHTEST_GRAY); } g2.fill(arrow); g2.dispose(); } /** * * This method gets the factor for enlargement or reduction of the arrow, if it should be * displayed in a non-standard size. * */ public float getSizeFactor() { return sizeFactor; } /** * * This method determines the factor for enlargement or reduction of the arrow, if it should * be displayed in a non-standard size. Standard is a width of 4 px and a height of 8 px. * * @param sizeFactor */ public void setSizeFactor(float sizeFactor) { this.sizeFactor = sizeFactor; } @Override public boolean isRolloverEnabled() { // force it to disable rollover because we have custom hover highlighting return false; } @Override public Dimension getPreferredSize() { Dimension prefSize = super.getPreferredSize(); prefSize.setSize(prefSize.getWidth() + 75, prefSize.getHeight() + 20); return prefSize; } @Override public Dimension getMinimumSize() { Dimension minSize = super.getMinimumSize(); minSize.setSize(75, minSize.getHeight()); return minSize; } } private final PopupMenuListener popupMenuListener = new PopupMenuListener() { @Override public void popupMenuWillBecomeVisible(PopupMenuEvent e) { popupVisible = true; arrowButton.getModel().setSelected(true); } @Override public void popupMenuWillBecomeInvisible(PopupMenuEvent e) { popupVisible = false; arrowButton.getModel().setSelected(false); ((JPopupMenu) e.getSource()).removePopupMenuListener(this); } @Override public void popupMenuCanceled(PopupMenuEvent e) { popupVisible = false; } }; private final ChangeListener changeListener = new ChangeListener() { @Override public void stateChanged(ChangeEvent e) { arrowButton.repaint(); mainButton.repaint(); } }; protected JButton mainButton = this; protected final DropDownArrowButton arrowButton = new DropDownArrowButton(this); protected boolean popupVisible = false; private final JToolBar arrowButtonPanel = new JToolBar(); private final JPanel emptyPanel = new JPanel(); /** flag indicating if the button is hovered */ private boolean hovered; private MouseListener hoverListener = new MouseAdapter() { @Override public void mouseExited(MouseEvent e) { hovered = false; setBorderPainted(false); arrowButton.setBorderPainted(false); setForeground(NORMAL_TEXTCOLOR); repaint(); } @Override public void mouseEntered(MouseEvent e) { hovered = true; setBorderPainted(true); arrowButton.setBorderPainted(true); setForeground(HOVERED_TEXTCOLOR); repaint(); } }; private FocusListener focusListener = new FocusAdapter() { @Override public void focusLost(FocusEvent e) { if (e.getOppositeComponent() != arrowButton && e.getOppositeComponent() != getPopupMenu()) { hovered = false; setBorderPainted(false); arrowButton.setBorderPainted(false); setForeground(NORMAL_TEXTCOLOR); repaint(); } } }; public FancyDropDownButton(Action mainAction, Action arrowAction, boolean showText) { super(mainAction); if (!showText) { mainButton.setText(null); } mainButton.setOpaque(false); arrowButton.setOpaque(false); mainButton.setBorderPainted(false); mainButton.setMargin(new Insets(0, 0, 0, 0)); mainButton.getModel().addChangeListener(changeListener); arrowButton.getModel().addChangeListener(changeListener); if (arrowAction != null) { arrowButton.addActionListener(arrowAction); } else { arrowButton.addActionListener(new DefaultArrowAction()); } arrowButton.setMargin(new Insets(0, 0, 0, 0)); mainButton.addPropertyChangeListener("enabled", new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { arrowButton.setEnabled(mainButton.isEnabled()); } }); arrowButtonPanel.setLayout(new CardLayout()); arrowButtonPanel.setFloatable(false); arrowButtonPanel.setBorder(null); arrowButton.setBorderPainted(true); emptyPanel.setOpaque(false); arrowButtonPanel.setOpaque(false); // setup fancy button things addMouseListener(hoverListener); addArrowButtonMouseListener(hoverListener); addFocusListener(focusListener); setBorderPainted(false); setBorder(new DropShadowBorder(SHADOW_COLOR, 5, 0.5f, 12, false, false, true, true)); arrowButton.setBorder(new DropShadowBorder(SHADOW_COLOR, 5, 0.5f, 12, false, false, true, true)); setFocusPainted(false); setContentAreaFilled(false); setHorizontalAlignment(SwingConstants.LEFT); setHorizontalTextPosition(SwingConstants.RIGHT); setForeground(NORMAL_TEXTCOLOR); setFont(new Font("Open Sans Light", Font.PLAIN, 28)); setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); arrowButton.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); } public FancyDropDownButton(Action action, boolean showText) { this(action, null, showText); } /** * Shows no text on buttons. */ public FancyDropDownButton(Action action) { this(action, false); } public void addArrowButtonMouseListener(MouseListener l) { arrowButton.addMouseListener(l); emptyPanel.addMouseListener(l); } public void removeArrowButtonMouseListener(MouseListener l) { arrowButton.removeMouseListener(l); emptyPanel.removeMouseListener(l); } protected abstract JPopupMenu getPopupMenu(); public void add(Action action) { getPopupMenu().add(action); } public void add(JMenuItem item) { getPopupMenu().add(item); } public JButton addToToolBar(JToolBar toolbar) { arrowButtonPanel.add(arrowButton); arrowButtonPanel.add(emptyPanel); toolbar.add(mainButton); toolbar.add(arrowButtonPanel); return mainButton; } public JButton addToToolBar(JToolBar toolbar, Object mainButtonConstraints, Object arrowButtonConstraints) { arrowButtonPanel.add(arrowButton); arrowButtonPanel.add(emptyPanel); toolbar.add(mainButton, mainButtonConstraints); toolbar.add(arrowButtonPanel, arrowButtonConstraints); return mainButton; } public JButton addToToolbar(JPanel toolbar, Object mainButtonConstraints, Object arrowButtonConstraints) { arrowButtonPanel.add(arrowButton); arrowButtonPanel.add(emptyPanel); toolbar.add(mainButton, mainButtonConstraints); toolbar.add(arrowButtonPanel, arrowButtonConstraints); return mainButton; } public JButton addToToolBar(VLToolBar toolbar) { arrowButtonPanel.add(arrowButton); arrowButtonPanel.add(emptyPanel); toolbar.add(mainButton); toolbar.add(arrowButtonPanel); return mainButton; } public JButton addToToolBar(ViewToolBar toolbar, int alignment) { arrowButtonPanel.add(arrowButton); arrowButtonPanel.add(emptyPanel); toolbar.add(mainButton, alignment); toolbar.add(arrowButtonPanel, alignment); return mainButton; } public JButton addToToolbar(JToolBar toolbar, Object mainButtonConstraints, Object arrowButtonConstraints) { arrowButtonPanel.add(arrowButton); arrowButtonPanel.add(emptyPanel); toolbar.add(mainButton, mainButtonConstraints); toolbar.add(arrowButtonPanel, arrowButtonConstraints); return mainButton; } public JButton addToFlowLayoutPanel(JPanel panel) { arrowButtonPanel.add(arrowButton); arrowButtonPanel.add(emptyPanel); panel.add(mainButton); panel.add(arrowButtonPanel); return mainButton; } public JButton addArrowToFlowLayoutPanel(JPanel panel) { // mainButton.setVisible(false); panel.add(mainButton); panel.add(arrowButton); return mainButton; } public void setArrowButtonVisible(boolean b) { CardLayout cl = (CardLayout) arrowButtonPanel.getLayout(); if (b) { cl.first(arrowButtonPanel); } else { cl.last(arrowButtonPanel); } } public boolean isArrowButtonVisible() { return arrowButton.isVisible(); } public void setArrowSizeFactor(float factor) { arrowButton.setSizeFactor(factor); } public float getArrowSizeFactor() { return arrowButton.getSizeFactor(); } public boolean isPopupMenuVisible() { return popupVisible; } /** * Returns <code>true</code> if the mouse currently hovers over this button. * * @return */ public boolean isHovered() { return hovered; } @Override public Dimension getPreferredSize() { Dimension prefSize = super.getPreferredSize(); prefSize.setSize(prefSize.getWidth() + 75, prefSize.getHeight() + 20); return prefSize; } @Override public boolean isRolloverEnabled() { // force it to disable rollover because we have custom hover highlighting return false; } @Override public void paintComponent(Graphics g) { if (getModel().isArmed()) { ((Graphics2D) g).translate(1.1, 1.1); } ((Graphics2D) g).setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); Graphics2D g2 = (Graphics2D) g.create(); if (hovered) { // fill background white g2.setColor(BACKGROUND_COLOR); g2.fillRect(0, 0, getWidth() - 6, getHeight() - 6); // draw border which complements the DropShadow g2.setColor(BORDER_LIGHTEST_GRAY); g2.drawLine(0, 0, 0, getHeight() - 6); g2.drawLine(0, getHeight() - 6, getWidth() - 6, getHeight() - 6); g2.drawLine(0, 0, getWidth() - 6, 0); g2.drawLine(getWidth() - 6, 0, getWidth() - 6, getHeight() - 6); // cut off arrow ends so the cut looks like an imaginary vertical line g2.setColor(BACKGROUND_COLOR); g2.fillRect((int) (getWidth() * 0.81), (int) (getHeight() * 0.29), 7, (int) (getHeight() * 0.5)); } g2.dispose(); super.paintComponent(g); } // factory methods public static FancyDropDownButton makeDropDownButton(Action mainAction, Action... actions) { final JPopupMenu menu = new JPopupMenu(); for (Action action : actions) { menu.add(action); } return new FancyDropDownButton(mainAction) { private static final long serialVersionUID = -7359018188605409766L; @Override protected JPopupMenu getPopupMenu() { return menu; } }; } public static FancyDropDownButton makeDropDownButton(Action action) { final JPopupMenu menu = new JPopupMenu(); return new FancyDropDownButton(action) { private static final long serialVersionUID = -7359018188605409766L; @Override protected JPopupMenu getPopupMenu() { return menu; } }; } }