package org.geogebra.desktop.gui.util; import java.awt.Color; import java.awt.Dimension; import java.awt.Point; import java.awt.event.ActionEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import javax.swing.BorderFactory; import javax.swing.Icon; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JPopupMenu; import javax.swing.JSlider; import javax.swing.SwingConstants; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import org.geogebra.common.awt.GColor; import org.geogebra.common.gui.util.SelectionTable; import org.geogebra.desktop.main.AppD; import org.geogebra.desktop.util.GuiResourcesD; /** * JButton with popup component. A mouse click on the left side of the button * fires a normal action event. A mouse click on the right side triggers a popup * with either a selection table, a slider or both. Events generated by the * popup are passed up to the button invoker as action events. * * * @author G. Sturr * */ @SuppressWarnings("javadoc") public class PopupMenuButtonD extends JButton implements ChangeListener { private static final long serialVersionUID = 1L; private static final int CLICK_DOWN_ARROW_WIDTH = 20; private SelectionTable mode; private Object[] data; private AppD app; private PopupMenuButtonD thisButton; private JPopupMenu myPopup; public JPopupMenu getMyPopup() { return myPopup; } private JSlider mySlider; public void setFgColor(GColor fgColor) { if (myTable != null) { myTable.setFgColor(fgColor); } updateGUI(); } private SelectionTableD myTable; public SelectionTableD getMyTable() { return myTable; } private Dimension iconSize; public void setIconSize(Dimension iconSize) { this.iconSize = iconSize; } private boolean hasTable; // flag to determine if the popup should persist after a mouse click private boolean keepVisible = true; private boolean isDownwardPopup = true; public void setDownwardPopup(boolean isDownwardPopup) { this.isDownwardPopup = isDownwardPopup; } private boolean isStandardButton = false; public void setStandardButton(boolean isStandardButton) { this.isStandardButton = isStandardButton; } private boolean isFixedIcon = false; private boolean isIniting = true; protected boolean popupIsVisible; /* * #*********************************** /** Button constructors */ /** * @param app */ public PopupMenuButtonD(AppD app) { this(app, null, -1, -1, null, SelectionTable.UNKNOWN, false, false); } /** * @param app * @param data * @param rows * @param columns * @param iconSize * @param mode */ public PopupMenuButtonD(AppD app, Object[] data, int rows, int columns, Dimension iconSize, SelectionTable mode) { this(app, data, rows, columns, iconSize, mode, true, false); } /** * @param app * @param data * @param rows * @param columns * @param iconSize * @param mode * @param hasTable * @param hasSlider */ public PopupMenuButtonD(final AppD app, Object[] data, int rows, int columns, Dimension iconSize, SelectionTable mode, final boolean hasTable, boolean hasSlider) { super(); this.app = app; this.hasTable = hasTable; this.mode = mode; this.iconSize = iconSize; this.thisButton = this; this.setFocusable(false); // create the popup myPopup = new JPopupMenu(); myPopup.setFocusable(false); myPopup.setBackground(Color.WHITE); myPopup.setBorder(BorderFactory.createCompoundBorder( BorderFactory.createLineBorder(Color.GRAY), BorderFactory.createEmptyBorder(3, 3, 3, 3))); // add a mouse listener to our button that triggers the popup addMouseListener(new MouseAdapter() { @Override public void mouseEntered(MouseEvent e) { popupIsVisible = isPopupShowing(); } @Override public void mousePressed(MouseEvent e) { onMousePressed(e.getX()); } }); // place text to the left of drop down icon this.setHorizontalTextPosition(SwingConstants.LEFT); this.setHorizontalAlignment(SwingConstants.LEFT); // create selection table if (hasTable) { this.data = data; myTable = new SelectionTableD(app, data, rows, columns, iconSize, mode); setSelectedIndex(0); // add a mouse listener to handle table selection myTable.addMouseListener(new MouseAdapter() { @Override public void mouseReleased(MouseEvent e) { handlePopupActionEvent(); } }); /* * // if displaying text only, then adjust the width if(mode == * SelectionTable.MODE_TEXT){ Dimension d = this.getPreferredSize(); * d.width = myTable.getColumnWidth(); setMinimumSize(d); * setMaximumSize(d); } */ myTable.setBackground(myPopup.getBackground()); myPopup.add(myTable); } // create slider if (hasSlider) { getMySlider(); } isIniting = false; if (mode == SelectionTable.MODE_TEXT && iconSize.width == -1) { iconSize.width = myTable.getColumnWidth() - 4; iconSize.height = myTable.getRowHeight() - 4; } } protected boolean isPopupShowing() { return myPopup.isShowing(); } protected void onMousePressed(int x) { if (!thisButton.isEnabled()) { return; } if (popupIsVisible && !myPopup.isVisible()) { popupIsVisible = false; return; } if (!prepareToShowPopup()) { return; } Point locButton = getLocation(); final int clicDownArrowWidth = (int) Math .round((CLICK_DOWN_ARROW_WIDTH * (app.getScaledIconSize() / 16.0))); // trigger popup // default: trigger only when the mouse is over the right side // of the button // if isStandardButton: pressing anywhere triggers the popup if (isStandardButton || x >= getWidth() - clicDownArrowWidth && x <= getWidth()) { if (hasTable) { myTable.updateFonts(); } if (isDownwardPopup) { // popup appears below the button myPopup.show(getParent(), locButton.x, locButton.y + getHeight()); } else { // popup appears above the button myPopup.show(getParent(), locButton.x - myPopup.getPreferredSize().width + thisButton.getWidth(), locButton.y - myPopup.getPreferredSize().height - 2); } } popupIsVisible = myPopup.isShowing(); } /** * Prepares the popup before it is shown. Override this if the popup needs * special handling before opening. * * @return true if not overriden */ public boolean prepareToShowPopup() { return true; } public void addPopupMenuItem(JComponent component) { myPopup.add(component); } public void removePopupMenuItem(JComponent component) { myPopup.remove(component); } public void removeAllMenuItems() { myPopup.removeAll(); } public void setPopupMenu(JPopupMenu menu) { myPopup = menu; } /** * Override processMouseEvents to prevent firing a mouseReleased event and * the resulting ActionPerformed event when the mouse is clicked in the * dropdown triangle region. Clicking in this part of the button should just * trigger the popup. ActionPerformed events will be fired by the popup * following user selection. */ @Override protected void processMouseEvent(MouseEvent e) { if (e.getID() == MouseEvent.MOUSE_RELEASED) { // mouse is over the popup triangle side of the button if (isStandardButton || e.getX() >= getWidth() - app.getScaledIconSize() && e.getX() <= getWidth()) { return; } } super.processMouseEvent(e); } /** * @param geos * geo elements */ public void update(Object[] geos) { // override in subclasses } // ============================================= // GUI // ============================================= private void updateGUI() { if (isIniting) { return; } setIcon(getButtonIcon()); if (hasTable) { myTable.repaint(); } repaint(); } /** * Create our JSlider */ private void initSlider() { mySlider = new JSlider(0, 100); mySlider.setMajorTickSpacing(25); mySlider.setMinorTickSpacing(5); mySlider.setPaintTicks(false); mySlider.setPaintLabels(false); // mySlider.setSnapToTicks(true); mySlider.addChangeListener(this); // set slider dimensions Dimension d = mySlider.getPreferredSize(); if (hasTable) { d.width = myTable.getPreferredSize().width; } else { d.width = 110; } mySlider.setPreferredSize(d); mySlider.setBackground(myPopup.getBackground()); myPopup.add(mySlider); } // ============================================== // Handlers and Listeners // ============================================== /** * Pass a popup action event up to the button invoker. If the first button * click triggered our popup (the click was in the triangle region), then we * must pass action events from the popup to the invoker */ public void handlePopupActionEvent() { // System.out.println("handlepopup"); this.fireActionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, getActionCommand())); updateGUI(); if (!keepVisible) { myPopup.setVisible(false); } } /** * Change listener for slider. Fires an action event up to the button * invoker. */ @Override public void stateChanged(ChangeEvent e) { // if (mySlider.getValueIsAdjusting()) return; if (mySlider != null) { setSliderValue(mySlider.getValue()); } // System.out.println(mySlider.getValue()); this.fireActionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, getActionCommand())); updateGUI(); } // ============================================== // Getters/Setters // ============================================== public int getSelectedIndex() { return myTable.getSelectedIndex(); } public Object getSelectedValue() { return myTable.getSelectedValue(); } public void setSelectedIndex(Integer selectedIndex0) { int selectedIndex; if (selectedIndex0 == null) { selectedIndex = -1; } else { selectedIndex = selectedIndex0.intValue(); } myTable.setSelectedIndex(selectedIndex); updateGUI(); } public int getSliderValue() { return mySlider.getValue(); } public void setSliderValue(int value) { mySlider.removeChangeListener(this); mySlider.setValue(value); mySlider.addChangeListener(this); if (hasTable) { myTable.setSliderValue(value); } updateGUI(); } public JSlider getMySlider() { if (mySlider == null) { initSlider(); } return mySlider; } public void setKeepVisible(boolean keepVisible) { this.keepVisible = keepVisible; } /** * sets the tooTip strings for the menu selection table; the toolTipArray * should have a 1-1 correspondence with the data array * * @param toolTipArray */ public void setToolTipArray(String[] toolTipArray) { myTable.setToolTipArray(toolTipArray); } // ============================================== // Icon Handling // ============================================== public ImageIcon getButtonIcon() { ImageIcon icon = (ImageIcon) this.getIcon(); if (isFixedIcon) { return icon; } // draw the icon for the current table selection if (hasTable) { switch (mode) { case MODE_TEXT: // Strings are converted to icons. We don't use setText so that // the button size can be controlled // regardless of the layout manager. icon = GeoGebraIconD.createStringIcon( (String) data[getSelectedIndex()], app.getPlainFont(), false, false, true, iconSize, Color.BLACK, null); break; case MODE_ICON: case MODE_LATEX: icon = (ImageIcon) myTable.getSelectedValue(); break; default: icon = myTable.getDataIcon(data[getSelectedIndex()]); } } return icon; } /** * Append a downward triangle image to the right hand side of an input icon. */ @Override public void setIcon(Icon icon0) { if (isFixedIcon) { super.setIcon(icon0); return; } Icon icon = icon0; if (iconSize == null) { if (icon != null) { iconSize = new Dimension(icon.getIconWidth(), icon.getIconHeight()); } else { iconSize = new Dimension(1, 1); } } if (icon == null) { // icon = GeoGebraIcon.createEmptyIcon(1, iconSize.height); } else { icon = GeoGebraIconD.ensureIconSize((ImageIcon) icon, iconSize); } // add a down_triangle image to the left of the icon if (icon != null) { super.setIcon(GeoGebraIconD.joinIcons((ImageIcon) icon, app.getScaledIcon(GuiResourcesD.TRIANGLE_DOWN))); } else { super.setIcon(app.getScaledIcon(GuiResourcesD.TRIANGLE_DOWN)); } } public void setFixedIcon(Icon icon) { isFixedIcon = true; setIcon(icon); } public void setIndex(int mode) { myTable.setSelectedIndex(mode); } }