/** * @file Tool.java * @brief Collection of various image editing tools. * * @section License * * Copyright (C) 2012 IsmAvatar <IsmAvatar@gmail.com> * Copyright (C) 2013 jimn346 <jds9496@gmail.com> * * This file is a part of JEIE. * * This program 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. * * 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 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 org.jeie; import static org.jeie.OptionComponent.emptyPanel; import java.awt.Color; import java.awt.Component; import java.awt.Dimension; import java.awt.Point; import java.awt.Rectangle; import java.awt.event.MouseEvent; import javax.swing.BoxLayout; import javax.swing.JComponent; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import org.jeie.Canvas.RenderMode; import org.jeie.ImageAction.*; import org.jeie.OptionComponent.FillOptions; import org.jeie.OptionComponent.FillOptions.FillType; import org.jeie.OptionComponent.GradientOptions; import org.jeie.OptionComponent.SizeOptions; import org.jeie.OptionComponent.TextOptions; import org.jeie.resources.Resources; public interface Tool { void mousePress(MouseEvent e, Canvas c, Palette p); void mouseRelease(MouseEvent e, Canvas c, Palette p); /** * @return Returns an options widget for controlling tool settings. */ public JComponent getOptionsComponent(); /** * Invoked when the mouse moved, regardless of button states. * <p> * Notice that e.getButtons returns 0, because this event is * independent of mouse buttons - it can occur with no button, * or any number of buttons held down simultaneously. Buttons * can be fetched using <code>e.getModifiersEx & BUTTON1_DOWN_MASK</code>. * <p> * Because this is not linked to a single button, getting the * palette color should either be avoided or iterated per button. * @param e Class describing this mouse event. * @param c The Canvas for which the event is fired. * @param p The current color Palette. * @param drag Whether 1 or more buttons are down during this event. */ void mouseMove(MouseEvent e, Canvas c, Palette p, boolean drag); void finish(Canvas c, Palette p); /** * Convenience Tool parent class. This provides 4 convenience features: * <li><code>active</code> variable to keep track of the currently active action. * <li><code>finish</code> is implemented to simply commit <code>active</code> and reset it. * <li><code>isValid</code> static method to determine whether the mouse event is even valid for our image. * <li><code>cancel</code> method is offered to cancel (reject) the active action. * @param <K> The class of undo action generated by this tool (a child of ImageAction). */ public static abstract class GenericTool<K extends ImageAction> implements Tool { /** * The currently active action. Extending classes should be sure to set this variable and push it to canvas.active. */ protected K active; public abstract JComponent getOptionsComponent(); /** * Implemented to simply commit <code>active</code> and reset it. * Also resets canvas.active and redraws the canvas cache. * @param c The Canvas on which we are operating. * @param p The current Palette. */ public void finish(Canvas c, Palette p) { if (active == null) return; c.acts.add(active); c.active = active = null; c.redoActs.clear(); c.redrawCache(); } /** * Convenience static method to determine whether a mouse event is valid for the image. * Namely, this will check mouse coordinates, to ensure that they are within image bounds (0,0,w,h). * <p> * If a palette is provided (non-null), this will check that the mouse button corresponds with a palette color. * For instance, if the user presses the Middle mouse button, which has no corresponding color, it will still fire a * mousePress event, but the tool will not be able to draw because there is no color to draw with. * Setting the palette to null will omit this check and only perform a bounds check. * <p> * Notice: When using this function in mouseMove events, always set the palette to null. * This is because mouseMove events are not dependent on a single button, even during a drag. * As such, if you attempt to provide a palette in mouseMove, this function will always return false. * @param e The MouseEvent. This argument is mandatory. * @param c The Canvas. This argument is mandatory. * @param pal The Palette. Optional. Provide if you want to additionally confirm the mouse button has a palette color. * @return Whether this mouse event is in-bounds of our image and (optionally) has a palette color. */ public static boolean isValid(MouseEvent e, Canvas c, Palette pal) { if (pal != null && pal.getSelectedColor(e.getButton()) == null) return false; Point p = e.getPoint(); if (p.x < 0 || p.y < 0) return false; Dimension d = c.getImageSize(); if (p.x >= d.width || p.y >= d.height) return false; return true; } /** * Cancels (rejects/resets) the active action. * Also resets canvas.active and repaints the canvas. * @param c The canvas whose tool action is being cancelled. */ public void cancel(Canvas c) { c.active = active = null; c.repaint(); } } static final SizeOptions so = new SizeOptions(); static final FillOptions fills = new FillOptions(); static final GradientOptions go = new GradientOptions(); static final TextOptions opts = new TextOptions(); public static class LineTool extends GenericTool<LineAction> implements ChangeListener { long mouseTime; int button; int diameter; public void mousePress(MouseEvent e, Canvas canvas, Palette p) { if (active != null) { if (e.getButton() == button) finish(canvas,p); else cancel(canvas); return; } if (!isValid(e,canvas,p) && canvas.renderMode != RenderMode.TILED) return; button = e.getButton(); mouseTime = e.getWhen(); canvas.active = active = new LineAction(canvas, e.getPoint(),p.getSelectedColor(button),diameter); canvas.repaint(); } public void mouseRelease(MouseEvent e, Canvas canvas, Palette pal) { if (e.getWhen() - mouseTime < 200) return; if (active != null) active.p2 = e.getPoint(); finish(canvas,pal); } public void mouseMove(MouseEvent e, Canvas canvas, Palette p, boolean drag) { if (active != null && !active.p2.equals(e.getPoint())) { Rectangle r = new Rectangle(active.p2); //previous value active.p2 = e.getPoint(); r.add(active.p1); r.add(active.p2); r.x -= active.diameter << 1; r.y -= active.diameter << 1; r.width += active.diameter << 2; r.height += active.diameter << 2; canvas.repaint(r); } } public LineTool() { so.addChangeListener(this); } @Override public JComponent getOptionsComponent() { return so; } public void stateChanged(ChangeEvent e) { diameter = so.getValue(); } } public static class GradientTool extends GenericTool<GradientAction> implements ListSelectionListener { long mouseTime; int button; GradientOptions.GradientType type; public void mousePress(MouseEvent e, Canvas canvas, Palette p) { if (active != null) { if (e.getButton() == button) finish(canvas,p); else cancel(canvas); return; } button = e.getButton(); if (!isValid(e,canvas,p) && canvas.renderMode != RenderMode.TILED) return; mouseTime = e.getWhen(); Color c1, c2; if (button == MouseEvent.BUTTON1) { c1 = p.getLeft(); c2 = p.getRight(); } else { c1 = p.getRight(); c2 = p.getLeft(); } canvas.active = active = new GradientAction(canvas,e.getPoint(),c1,c2,type); canvas.repaint(); } public void mouseRelease(MouseEvent e, Canvas canvas, Palette pal) { if (e.getWhen() - mouseTime < 200) return; if (active != null) active.p2 = e.getPoint(); finish(canvas,pal); } public void mouseMove(MouseEvent e, Canvas canvas, Palette p, boolean drag) { if (active != null && !active.p2.equals(e.getPoint())) { active.p2 = e.getPoint(); canvas.repaint(); } } public GradientTool() { go.addListSelectionListener(this); type = go.getFillType(); } @Override public JComponent getOptionsComponent() { return go; } public void valueChanged(ListSelectionEvent e) { type = go.getFillType(); } } public static class PointTool extends GenericTool<PointAction> { @Override public JComponent getOptionsComponent() { return emptyPanel; } public void mousePress(MouseEvent e, Canvas c, Palette p) { if (active != null) { cancel(c); return; } if (!isValid(e,c,p) && c.renderMode != RenderMode.TILED) return; c.active = active = new PointAction(c, p.getSelectedColor(e.getButton())); active.add(e.getPoint()); c.repaint(); } public void mouseRelease(MouseEvent e, Canvas c, Palette p) { finish(c,p); } public void mouseMove(MouseEvent e, Canvas c, Palette p, boolean drag) { if (active != null) { Point pt = e.getPoint(); if (!active.pts.isEmpty() && active.pts.getLast().equals(pt)) return; Rectangle r = new Rectangle(pt); if (!active.pts.isEmpty()) r.add(active.pts.getLast()); active.add(pt); c.repaint(r); } } } public static class PaintbrushTool extends GenericTool<PaintbrushAction> implements ChangeListener { private static JPanel op; private int diameter; public PaintbrushTool() { so.addChangeListener(this); } @Override public JComponent getOptionsComponent() { op = new JPanel(); op.setLayout(new BoxLayout(op,BoxLayout.PAGE_AXIS)); op.add(so); return op; } public void stateChanged(ChangeEvent e) { diameter = so.getValue(); } public void mousePress(MouseEvent e, Canvas c, Palette p) { if (active != null) { cancel(c); return; } if (!isValid(e,c,p) && c.renderMode != RenderMode.TILED) return; c.active = active = new PaintbrushAction(c, p.getSelectedColor(e.getButton()), diameter); active.add(e.getPoint()); c.repaint(); } public void mouseRelease(MouseEvent e, Canvas c, Palette p) { finish(c,p); } public void mouseMove(MouseEvent e, Canvas c, Palette p, boolean drag) { if (active != null) { Point pt = e.getPoint(); if (!active.pts.isEmpty() && active.pts.getLast().equals(pt)) return; Rectangle r = new Rectangle(pt); r.x -= active.diameter << 1; r.y -= active.diameter << 1; r.width += active.diameter << 2; r.height += active.diameter << 2; if (!active.pts.isEmpty()) r.add(active.pts.getLast()); active.add(pt); c.repaint(r); } } } public static class RectangleTool extends GenericTool<RectangleAction> implements ListSelectionListener { long mouseTime; int button; FillType type = FillType.OUTLINE; public void mousePress(MouseEvent e, Canvas canvas, Palette p) { if (active != null) { if (e.getButton() == button) finish(canvas,p); else cancel(canvas); return; } if (!isValid(e,canvas,p)) return; button = e.getButton(); mouseTime = e.getWhen(); Color c1 = p.getLeft(); Color c2 = p.getRight(); if (button != MouseEvent.BUTTON1) { //lol, shut up c1 = c2; c2 = p.getLeft(); } switch (type) { case OUTLINE: canvas.active = active = new RectangleAction(canvas, e.getPoint(),c1,null); break; case BOTH: canvas.active = active = new RectangleAction(canvas, e.getPoint(),c1,c2); break; case FILL: canvas.active = active = new RectangleAction(canvas, e.getPoint(),c1,c1); break; } canvas.repaint(); } public void mouseRelease(MouseEvent e, Canvas canvas, Palette pal) { if (e.getWhen() - mouseTime < 200) return; if (active != null) active.p2 = e.getPoint(); finish(canvas,pal); } public void mouseMove(MouseEvent e, Canvas canvas, Palette p, boolean drag) { if (active != null && !active.p2.equals(e.getPoint())) { Rectangle r = new Rectangle(active.p2); //previous value active.p2 = e.getPoint(); r.add(active.p1); r.add(active.p2); canvas.repaint(r); } } @Override public JComponent getOptionsComponent() { return fills; } public RectangleTool() { fills.addListSelectionListener(this); } public void valueChanged(ListSelectionEvent e) { type = fills.getFillType(); } } public static class RoundRectangleTool extends GenericTool<RoundRectangleAction> implements ListSelectionListener { long mouseTime; int button; FillType type = FillType.OUTLINE; public void mousePress(MouseEvent e, Canvas canvas, Palette p) { if (active != null) { if (e.getButton() == button) finish(canvas,p); else cancel(canvas); return; } if (!isValid(e,canvas,p)) return; button = e.getButton(); mouseTime = e.getWhen(); Color c1 = p.getLeft(); Color c2 = p.getRight(); if (button != MouseEvent.BUTTON1) { //lol, shut up c1 = c2; c2 = p.getLeft(); } switch (type) { case OUTLINE: canvas.active = active = new RoundRectangleAction(canvas, e.getPoint(),c1,null); break; case BOTH: canvas.active = active = new RoundRectangleAction(canvas, e.getPoint(),c1,c2); break; case FILL: canvas.active = active = new RoundRectangleAction(canvas, e.getPoint(),c1,c1); break; } canvas.repaint(); } public void mouseRelease(MouseEvent e, Canvas canvas, Palette pal) { if (e.getWhen() - mouseTime < 200) return; if (active != null) active.p2 = e.getPoint(); finish(canvas,pal); } public void mouseMove(MouseEvent e, Canvas canvas, Palette p, boolean drag) { if (active != null && !active.p2.equals(e.getPoint())) { Rectangle r = new Rectangle(active.p2); //previous value active.p2 = e.getPoint(); r.add(active.p1); r.add(active.p2); canvas.repaint(r); } } @Override public JComponent getOptionsComponent() { return fills; } public RoundRectangleTool() { fills.addListSelectionListener(this); } public void valueChanged(ListSelectionEvent e) { type = fills.getFillType(); } } public static class OvalTool extends GenericTool<OvalAction> implements ListSelectionListener { long mouseTime; int button; FillType type = FillType.OUTLINE; public void mousePress(MouseEvent e, Canvas canvas, Palette p) { if (active != null) { if (e.getButton() == button) finish(canvas,p); else cancel(canvas); return; } if (!isValid(e,canvas,p)) return; button = e.getButton(); mouseTime = e.getWhen(); Color c1 = p.getLeft(); Color c2 = p.getRight(); if (button != MouseEvent.BUTTON1) { //lol, shut up c1 = c2; c2 = p.getLeft(); } switch (type) { case OUTLINE: canvas.active = active = new OvalAction(canvas, e.getPoint(),c1,null); break; case BOTH: canvas.active = active = new OvalAction(canvas, e.getPoint(),c1,c2); break; case FILL: canvas.active = active = new OvalAction(canvas, e.getPoint(),c1,c1); break; } canvas.repaint(); } public void mouseRelease(MouseEvent e, Canvas canvas, Palette pal) { if (e.getWhen() - mouseTime < 200) return; if (active != null) active.p2 = e.getPoint(); finish(canvas,pal); } public void mouseMove(MouseEvent e, Canvas canvas, Palette p, boolean drag) { if (active != null && !active.p2.equals(e.getPoint())) { Rectangle r = new Rectangle(active.p2); //previous value active.p2 = e.getPoint(); r.add(active.p1); r.add(active.p2); canvas.repaint(r); } } @Override public JComponent getOptionsComponent() { return fills; } public OvalTool() { fills.addListSelectionListener(this); } public void valueChanged(ListSelectionEvent e) { type = fills.getFillType(); } } public static class FillTool extends GenericTool<FillAction> implements ListSelectionListener { FillType type; public void mousePress(MouseEvent e, Canvas c, Palette p) { if (active != null) { cancel(c); return; } Point fp; if (!isValid(e,c,p)) { if (c.renderMode != RenderMode.TILED) return; fp = e.getPoint(); fp.x = (fp.x + c.imageWidth()) % c.imageWidth(); fp.y = (fp.y + c.imageHeight()) % c.imageHeight(); System.out.println(fp.x + ", " + fp.y); System.out.println(c.imageWidth() + ", " + c.imageHeight()); } else fp = e.getPoint(); Color c1 = p.getLeft(); Color c2 = p.getRight(); if (e.getButton() != MouseEvent.BUTTON1) { c1 = c2; c2 = p.getLeft(); } switch (type) { case OUTLINE: c.active = active = new FillAction(c, c.getRenderImage(),fp,c1,null,0,type); break; case BOTH: c.active = active = new FillAction(c, c.getRenderImage(),fp,c1,c2,0,type); break; case FILL: c.active = active = new FillAction(c, c.getRenderImage(),fp,c1,c1,0,type); break; } c.repaint(); } public void mouseRelease(MouseEvent e, Canvas c, Palette p) { finish(c,p); } //Unused public void mouseMove(MouseEvent e, Canvas c, Palette p, boolean drag) { //Unused } @Override public JComponent getOptionsComponent() { return fills; } public FillTool() { fills.addListSelectionListener(this); fills.select(FillType.FILL); } public void valueChanged(ListSelectionEvent e) { type = fills.getFillType(); } } public static class ColorPickerTool extends GenericTool<ImageAction> { //Necessary for dragging as dragging gives no button by default. private int button; public void mousePress(MouseEvent e, Canvas c, Palette p) { button = e.getButton(); Point cp; if (!isValid(e,c,p)) { if (c.renderMode != RenderMode.TILED) return; cp = e.getPoint(); cp.x = (cp.x + c.imageWidth()) % c.imageWidth(); cp.y = (cp.y + c.imageHeight()) % c.imageHeight(); System.out.println(cp.x + ", " + cp.y); System.out.println(c.imageWidth() + ", " + c.imageHeight()); } else cp = e.getPoint(); if (e.getButton() == MouseEvent.BUTTON1) p.setLeft(c.getColorAt(cp)); else if (e.getButton() == MouseEvent.BUTTON3) p.setRight(c.getColorAt(cp)); } public void mouseRelease(MouseEvent e, Canvas c, Palette p) { //Unused } public void mouseMove(MouseEvent e, Canvas c, Palette p, boolean drag) { if (drag) mousePress(new MouseEvent((Component) e.getSource(),e.getID(),e.getWhen(),e.getModifiers(),e.getX(),e.getY(), e.getClickCount(),e.isPopupTrigger(),button), c, p); } public JComponent getOptionsComponent() { return emptyPanel; } } public static class TextTool extends GenericTool<ImageAction> { public void mousePress(MouseEvent e, Canvas c, Palette p) { Color col; if (e.getButton() == MouseEvent.BUTTON1) col = p.getLeft(); else if (e.getButton() == MouseEvent.BUTTON3) col = p.getRight(); else return; Point cp; if (!isValid(e,c,p)) { if (c.renderMode != RenderMode.TILED) return; cp = e.getPoint(); cp.x = (cp.x + c.imageWidth()) % c.imageWidth(); cp.y = (cp.y + c.imageHeight()) % c.imageHeight(); } else cp = e.getPoint(); String text = JOptionPane.showInputDialog(Resources.getString("TextTool.TEXT")); if (text == null) return; c.active = active = new TextAction(c, cp, col, opts.font, text, opts.halign, opts.valign); finish(c,p); } public void mouseRelease(MouseEvent e, Canvas c, Palette p) { //Unused } public void mouseMove(MouseEvent e, Canvas c, Palette p, boolean drag) { //Unused } public JComponent getOptionsComponent() { return opts; } } }