Java tutorial
/* * Copyright (c) 2008, SQL Power Group Inc. * * This file is part of DQguru * * DQguru is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * DQguru 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package ca.sqlpower.matchmaker.swingui; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Cursor; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Insets; import java.awt.Point; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.FocusEvent; import java.awt.event.FocusListener; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionAdapter; import java.awt.event.MouseMotionListener; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Callable; import javax.swing.AbstractAction; import javax.swing.BorderFactory; import javax.swing.JButton; import javax.swing.JColorChooser; import javax.swing.JDialog; import javax.swing.JFrame; import javax.swing.JMenu; import javax.swing.JMenuItem; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JTextArea; import javax.swing.SwingUtilities; import javax.swing.Timer; import javax.swing.border.Border; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import org.apache.log4j.Logger; import ca.sqlpower.matchmaker.swingui.munge.AbstractMungeComponent; import ca.sqlpower.matchmaker.swingui.munge.MungePen; import ca.sqlpower.swingui.DataEntryPanelBuilder; import ca.sqlpower.swingui.FontSelector; import com.jgoodies.forms.builder.DefaultFormBuilder; import com.jgoodies.forms.factories.ButtonBarFactory; import com.jgoodies.forms.layout.FormLayout; /** * This class will be used to add a Label to the MungePen. This class will build * a JPanel with some text and of a specific color. */ public class LabelPane extends JPanel { private static final Logger logger = Logger.getLogger(LabelPane.class); /** * The point to auto scroll to */ private Point autoScrollPoint; /** * This is the point relative to the top left corner of the Label */ private Point componentReferencePoint; /** * This is the MungePen that contains the Label. */ private MungePen mp; /** * The timer to handle when to autoscroll because always calling it is way * too fast. */ private Timer autoScrollTimer; /** * This boolean will indicate whether or not we are in a resize state of the * Label. */ private boolean isResizeState; /** * This is the default size of the label as well as the minimum size of the * label */ private static Dimension DEFAULT_SIZE = new Dimension(100, 50); /** * This is a mouse Listener on the Label that will listen for clicks. */ private MouseListener mouseAdapter; /** * This is another mouse listener that listens for drags */ private LabelMouseMoveListener moveListener; /** * This is a list of JTextArea objects that exist on the Label */ private List<JTextArea> textAreaList; /** * This is a JDialog that contains the JColorChooser, used to set color of * labels and their components */ private JDialog colorChanger; /** * This is a color chooser used to generate colors. */ private JColorChooser colorPanel; /** * This indicates if we are in a state in which a JTextArea object is being * dragged */ private boolean dragTextArea; /** * This indicates if we are in a state in which a label is being dragged */ private boolean dragLabel; /** * When choosing a color, this is the Component that we are making the color * change on. */ private Component currentComponent; /** * This is a hard-coded value that is mainly used in the revalidateComp * method such that dragging a JTextArea outside the borders of the Label * looks relatively good. */ private int DEFAULT_INSET = 7; /** * This is the previous point the label was at before it was dragged */ private Point previousLocation; /** * This will crate a new label with a certain color and a text caption. * * @param color * The color of the label * @param text * The text on the panel. */ public LabelPane(MungePen mp, Color color, Point p) { // Set the initial size on the label. setSize(DEFAULT_SIZE); textAreaList = new ArrayList<JTextArea>(); this.mp = mp; this.componentReferencePoint = new Point(); setUpColorChanger(); setLocation(p); setBackground(color); addListeners(); autoScrollTimer.stop(); autoScrollTimer.setRepeats(true); Border etchedBdr = BorderFactory.createEtchedBorder(); setMinimumSize(new Dimension(getSize())); setLayout(null); setBorder(etchedBdr); } /** * This method sets up the color chooser that is used on every component in * this label. */ private void setUpColorChanger() { colorChanger = new JDialog((JFrame) mp.getTopLevelAncestor().getParent(), true); colorPanel = new JColorChooser(getBackground()); FormLayout layout = new FormLayout("default", "default, default"); DefaultFormBuilder builder = new DefaultFormBuilder(layout); builder.setDefaultDialogBorder(); colorChanger.setLayout(new BorderLayout()); builder.add(colorPanel); JButton ok = new JButton("Ok"); JButton cancel = new JButton("Cancel"); JPanel okCancelBar = ButtonBarFactory.buildOKCancelBar(ok, cancel); cancel.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { colorChanger.setVisible(false); } }); ok.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { Color lastColorChosen = colorPanel.getColor(); Component comp = currentComponent; if (comp == null) { logger.error( "Why was this null?!?! check the " + "generateColorSelectSubMenu method and make sure " + "the component is being set before we get here"); } else if (comp instanceof LabelPane) { comp.setBackground(lastColorChosen); for (JTextArea a : textAreaList) { a.setBackground(lastColorChosen); } } else { comp.setForeground(lastColorChosen); } colorChanger.setVisible(false); } }); builder.nextRow(); builder.add(okCancelBar); colorChanger.getContentPane().add(builder.getPanel()); colorChanger.pack(); colorChanger.setLocationRelativeTo(this); } /** * This method will set up all the necessary listeners on the Label. */ private void addListeners() { mouseAdapter = new MouseAdapter() { /** * This will normalize the MungePen to make sure that no components * are moved out of the viewport. */ @Override public void mouseReleased(MouseEvent e) { mp.lockAutoScroll(false); autoScrollTimer.stop(); mp.normalize(); dragLabel = false; mp.stopConnection(); mp.revalidate(); } /** * This will tell us the coordinates of the point relative to the * top left corner of the Label. */ @Override public void mousePressed(MouseEvent e) { requestFocus(); determineReferencePoint(e); previousLocation = getLocation(); int y = (int) componentReferencePoint.getY(); int x = (int) componentReferencePoint.getX(); if (x > getWidth() - DEFAULT_INSET && y > getHeight() - DEFAULT_INSET) { isResizeState = true; } else { isResizeState = false; } } /** * This will invoke the right click menu for the Label */ @Override public void mouseClicked(MouseEvent arg0) { repaint(); if (SwingUtilities.isRightMouseButton(arg0)) { showRightClickMenu(arg0.getPoint()); } autoScrollTimer.stop(); } }; addMouseListener(mouseAdapter); moveListener = new LabelMouseMoveListener(); addMouseMotionListener(moveListener); autoScrollTimer = new Timer(AbstractMungeComponent.AUTO_SCROLL_TIME, new AbstractAction() { public void actionPerformed(ActionEvent e) { performAutoScroll(); } }); addFocusListener(new FocusListener() { public void focusGained(FocusEvent e) { repaint(); } public void focusLost(FocusEvent e) { if (getParent() != null) { getParent().repaint(); } if (autoScrollTimer.isRunning()) { autoScrollTimer.stop(); } } }); addMouseMotionListener(new MouseMotionListener() { public void mouseMoved(MouseEvent e) { determineReferencePoint(e); int y = (int) componentReferencePoint.getY(); int x = (int) componentReferencePoint.getX(); if (x > getWidth() - DEFAULT_INSET && y > getHeight() - DEFAULT_INSET) { setCursor(new Cursor(Cursor.HAND_CURSOR)); setToolTipOnLabel("To resize: Drag this corner"); } else { setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); setToolTipOnLabel(null); } } public void mouseDragged(MouseEvent arg0) { } }); } /** * Set a tooltip for the label * * @param string */ private void setToolTipOnLabel(String string) { setToolTipText(string); } /** * Move label to the lowest layer of the MungePne in order to avoid * overlapping with other MungeComponents, and possibly other labels. */ protected void moveLabelToBack() { mp.setLayer(this, mp.lowestLayer()); for (LabelPane l : mp.getLabels()) { if (l != this) { mp.setLayer(l, mp.getLayer(l) + 1); } } } /** * Move the labels to the highest layer of all the labels. */ protected void moveLabelToFront() { for (LabelPane l : mp.getLabels()) { if (l != this) { mp.setLayer(l, mp.getLayer(l) - 1); } } mp.setLayer(this, mp.findHighestLabelLayer()); } /** * This will set the ComponentReferencePoint value which will indicate the * x,y coordinates of the moues relative to the top left corner of the label * * @param e */ private void determineReferencePoint(MouseEvent e) { componentReferencePoint = new Point((int) (e.getX() - getX()), (int) (e.getY() - getY())); componentReferencePoint.translate(getX(), getY()); } /** * This will perform an autoscroll on a mouse dragged event. */ protected void performAutoScroll() { mp.autoscroll(autoScrollPoint, this); } /** * This will show a right-Click menu on the label. This currently only * contains a Delete Label action. * * @param p */ private void showRightClickMenu(Point p) { JPopupMenu popup = new JPopupMenu(); JMenuItem delete = new JMenuItem("Delete Label"); popup.add(delete); delete.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { removeFromMungePen(); } }); final Point point = p; popup.add(generateColorSelectSubMenu(this, true)); JMenuItem addText = new JMenuItem("Add Text"); popup.add(addText); addText.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { addText(point); } }); JMenuItem moveToBack = new JMenuItem("Move To Back"); popup.add(moveToBack); moveToBack.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { moveLabelToBack(); } }); JMenuItem moveToFront = new JMenuItem("Move To Front"); popup.add(moveToFront); moveToFront.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { moveLabelToFront(); } }); popup.show(this, (int) p.getX(), (int) p.getY()); } /** * This will generate a JMenu that can be used for selecting a color from a * defined list, or choosing a custom color using the JColorChooser * * @param comp * @param isBackgroundColor * @return */ private JMenu generateColorSelectSubMenu(final Component comp, final boolean isBackgroundColor) { JMenu menu = new JMenu("Change color"); for (final Color c : ColorScheme.BREWER_SET19) { JMenuItem mi = new JMenuItem(); JPanel i = new JPanel(); i.setBackground(c); mi.setPreferredSize(new Dimension(100, 20)); mi.add(i); mi.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { changeColor(comp, c, isBackgroundColor); if (comp instanceof LabelPane) { for (JTextArea a : textAreaList) { a.setBackground(c); } } } }); menu.add(mi); } JMenuItem changeColor = new JMenuItem("Custom Color"); changeColor.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { currentComponent = comp; logger.debug("currentComponent got set to " + comp); colorChanger.setVisible(true); } }); menu.add(changeColor); return menu; } /** * This method will add a new JTextArea component to the Label at a specific * point p. * * @param p */ protected void addText(Point p) { final JTextArea area = new JTextArea("<Enter_text_here>"); textAreaList.add(area); area.setBackground(null); area.setLocation(p); add(area); area.setBackground(getBackground()); area.setSize(area.getPreferredSize()); area.setToolTipText("To move: Hold ALT or Middle Mouse Button and drag"); area.repaint(); revalidateComp(area, false); area.getDocument().addDocumentListener(new DocumentListener() { public void removeUpdate(DocumentEvent e) { resizeThis(); } public void insertUpdate(DocumentEvent e) { resizeThis(); } private void resizeThis() { area.setSize(area.getPreferredSize()); area.repaint(); revalidateComp(area, false); } public void changedUpdate(DocumentEvent e) { } }); area.addKeyListener(new KeyAdapter() { public void keyReleased(KeyEvent e) { if ((e.getModifiersEx() & KeyEvent.ALT_DOWN_MASK) == 0) { dragTextArea = false; } } public void keyPressed(KeyEvent e) { if ((e.getModifiersEx() & KeyEvent.ALT_DOWN_MASK) != 0) { dragTextArea = true; } } }); area.addMouseMotionListener(new MouseMotionAdapter() { public void mouseDragged(MouseEvent e) { if (dragTextArea) { dragText(e, area); revalidateComp(area, false); } } }); area.addFocusListener(new FocusListener() { public void focusLost(FocusEvent e) { area.setBorder(null); area.setSize(area.getPreferredSize()); } public void focusGained(FocusEvent e) { area.setBorder(BorderFactory.createEtchedBorder()); area.setSize(area.getPreferredSize()); } }); area.addMouseListener(new MouseAdapter() { public void mouseExited(MouseEvent e) { if (!area.hasFocus()) { area.setBorder(null); area.setSize(area.getPreferredSize()); } } @Override public void mousePressed(MouseEvent e) { super.mousePressed(e); if (SwingUtilities.isMiddleMouseButton(e)) { dragTextArea = true; } } @Override public void mouseReleased(MouseEvent e) { super.mouseReleased(e); if (SwingUtilities.isMiddleMouseButton(e)) { dragTextArea = false; } } public void mouseEntered(MouseEvent e) { area.setBorder(BorderFactory.createEtchedBorder()); area.setSize(area.getPreferredSize()); } public void mouseClicked(MouseEvent e) { if (SwingUtilities.isRightMouseButton(e)) { JPopupMenu popupOnArea = new JPopupMenu(); JMenuItem delete = new JMenuItem("Delete Text"); popupOnArea.add(delete); popupOnArea.add(generateColorSelectSubMenu(area, false)); delete.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { textAreaList.remove(area); removeTextFromLabel(area); } }); JMenuItem font = new JMenuItem("Change Font"); popupOnArea.add(font); font.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { final FontSelector fontSelector = new FontSelector(area.getFont()); Callable<Boolean> okCall = new Callable<Boolean>() { public Boolean call() { logger.debug("We have changed the font to: " + fontSelector.getSelectedFont()); area.setFont(fontSelector.getSelectedFont()); area.setSize(area.getPreferredSize()); revalidateComp(area, false); return true; } }; Callable<Boolean> cancelCall = new Callable<Boolean>() { public Boolean call() { return true; } }; JDialog d = DataEntryPanelBuilder.createDataEntryPanelDialog(fontSelector, (JFrame) mp.getTopLevelAncestor().getParent(), "Choose a font", "OK", okCall, cancelCall); d.setVisible(true); } }); popupOnArea.show(area, (int) e.getX(), (int) e.getY()); } } }); revalidate(); } /** * This method will be dragged whenever we are attempting do drag a * JTextArea inside of the Label * * @param e * @param a * The JTextArea component we are trying to drag */ protected void dragText(MouseEvent e, JTextArea a) { a.setSelectionStart(0); a.setSelectionEnd(0); e.translatePoint((int) a.getLocation().getX(), (int) a.getLocation().getY()); e.translatePoint(-a.getSize().width / 2, -a.getSize().height / 2); a.setLocation((int) (e.getX()), (int) (e.getY())); } /** * This method will remove a JTextArea component from the Label * * @param area */ private void removeTextFromLabel(JTextArea area) { remove(area); repaint(); } /** * This method will change the color of a component in the label * * @param comp * The component we are doing the color change on * @param c * the new Color * @param isBackgroundColor * This indicates whether the new color should be set as a * background color (for the Label) or a foreground color (for * the text) */ private void changeColor(Component comp, Color c, boolean isBackgroundColor) { if (c == null) { colorChanger.setVisible(true); } else { if (isBackgroundColor) comp.setBackground(c); else comp.setForeground(c); } } /** * Remove the current component from the MungePen */ protected void removeFromMungePen() { mp.remove(this); mp.repaint(); } /** * This is a Mouse listener on the label which will be able to handLE * dragging. */ private class LabelMouseMoveListener extends MouseMotionAdapter { @Override public void mouseDragged(MouseEvent e) { dragMouse(e); for (JTextArea area : textAreaList) { revalidateComp(area, true); } } } /** * This method will get called whenever we get a mouseDragged event. This * method will handle dragging of the label, as well as auto-scrolling the * MungePen. Note: A timer is used to autoscroll because * * @param e */ public void dragMouse(MouseEvent e) { if (!isResizeState) { Point mouse = e.getPoint(); mouse.x += getX(); mouse.y += getY(); MungePen parent = (MungePen) getParent(); if (mouse.x < 0 || mouse.y < 0) { return; } if (!parent.isConnecting()) { // diff is set to null for a right click to prevent dragging if (componentReferencePoint != null) { // unlocks the bounds (because the pen has no idea when the // mouse is moving mp.lockAutoScroll(false); e.translatePoint(getX(), getY()); setLocation((int) (e.getX() - componentReferencePoint.getX()), (int) (e.getY() - componentReferencePoint.getY())); previousLocation = getLocation(); // checks if auto scrolling is a good idea at the present // time MungePen pen = mp; Insets autoScroll = pen.getAutoscrollInsets(); // does not use the mouse point because this looks better autoScrollPoint = new Point(pen.getWidth() / 2, pen.getHeight() / 2); boolean asChanged = false; if (getX() + getWidth() > pen.getWidth() - autoScroll.right) { autoScrollPoint.x = getX() + getWidth(); asChanged = true; } if (getY() + getHeight() > pen.getHeight() - autoScroll.bottom) { autoScrollPoint.y = getY() + getHeight(); asChanged = true; } if (getX() < autoScroll.left) { autoScrollPoint.x = getX(); asChanged = true; } if (getY() < autoScroll.top) { autoScrollPoint.y = getY(); asChanged = true; } if (!asChanged) { autoScrollTimer.stop(); } else { autoScrollTimer.restart(); } } } else { parent.mouseX = e.getX() + getX(); parent.mouseY = e.getY() + getY(); } mp.repaint(); } else { Point currentPoint = new Point((int) (e.getPoint().getX() - getX()), (int) (e.getPoint().getY() - getY())); currentPoint.translate(getX(), getY()); int xDiff = (int) (currentPoint.getX() - componentReferencePoint.getX()); int yDiff = (int) (currentPoint.getY() - componentReferencePoint.getY()); if (componentReferencePoint.getX() > getWidth() - DEFAULT_INSET && componentReferencePoint.getY() > getHeight() - DEFAULT_INSET) { setSize((int) getSize().getWidth() + xDiff, (int) getSize().getHeight() + yDiff); } determineReferencePoint(e); revalidateComp(null, false); } } /** * This method will re-validate the size of the Label. */ public void revalidateComp(Component c, boolean resize) { if (getSize().getWidth() < DEFAULT_SIZE.getWidth()) { setSize((int) DEFAULT_SIZE.getWidth(), (int) getSize().getHeight()); } if (getSize().getHeight() < DEFAULT_SIZE.getHeight()) { setSize((int) getSize().getWidth(), (int) DEFAULT_SIZE.getHeight()); } if (c != null) { if ((c.getLocation().x + c.getWidth()) > getSize().width - DEFAULT_INSET) { int xDiff = (c.getLocation().x + c.getWidth()) - getSize().width; setSize((int) getSize().getWidth() + xDiff + DEFAULT_INSET, (int) getSize().getHeight()); } if ((c.getLocation().y + c.getHeight()) > getSize().height) { int yDiff = (c.getLocation().y + c.getHeight()) - getSize().height; setSize((int) getSize().getWidth(), (int) getSize().getHeight() + yDiff + DEFAULT_INSET); } if (!resize) { if (c.getLocation().x <= 0) { setSize((int) getSize().getWidth() + Math.abs(c.getLocation().x), (int) getSize().getHeight()); setLocation(getLocation().x + c.getLocation().x, getLocation().y); for (JTextArea area : textAreaList) { if (area != c) { area.setLocation(area.getLocation().x - c.getLocation().x, area.getLocation().y); } } } if (c.getLocation().y <= 0) { setSize((int) getSize().getWidth(), (int) getSize().getHeight() + Math.abs(c.getLocation().y)); setLocation(getLocation().x, getLocation().y + c.getLocation().y); for (JTextArea area : textAreaList) { if (area != c) { area.setLocation(area.getLocation().x, area.getLocation().y - c.getLocation().y); } } } } } } /** * Paint the small box in the corner of the Label to show the resize drag * area. */ protected void paintComponent(Graphics g) { super.paintComponent(g); if (hasFocus()) { g.setColor(Color.BLACK); g.fillRect(getSize().width - DEFAULT_INSET, getSize().height - DEFAULT_INSET, DEFAULT_INSET, DEFAULT_INSET); } } }