/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ /* Part of the Processing project - http://processing.org Copyright (c) 2012-16 The Processing Foundation This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. 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, write to the Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package processing.mode.java.pdex; import java.awt.Cursor; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Point; import java.awt.RenderingHints; import java.awt.Toolkit; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.awt.image.BufferedImage; import java.util.List; import processing.app.SketchCode; import processing.app.syntax.PdeTextAreaPainter; import processing.app.syntax.TextAreaDefaults; import processing.mode.java.JavaEditor; import processing.mode.java.tweak.ColorControlBox; import processing.mode.java.tweak.ColorSelector; import processing.mode.java.tweak.Handle; import processing.mode.java.tweak.Settings; /** * Customized line painter to handle tweak mode features. */ public class JavaTextAreaPainter extends PdeTextAreaPainter { public JavaTextAreaPainter(final JavaTextArea textArea, TextAreaDefaults defaults) { super(textArea, defaults); // TweakMode code tweakMode = false; cursorType = Cursor.DEFAULT_CURSOR; } // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . // TWEAK MODE protected int horizontalAdjustment = 0; public boolean tweakMode = false; public List<List<Handle>> handles; public List<List<ColorControlBox>> colorBoxes; public Handle mouseHandle = null; public ColorSelector colorSelector; int cursorType; BufferedImage cursorImg = new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB); Cursor blankCursor; // this is a temporary workaround for the CHIP, will be removed { Dimension cursorSize = Toolkit.getDefaultToolkit().getBestCursorSize(16, 16); if (cursorSize.width == 0 || cursorSize.height == 0) { blankCursor = Cursor.getDefaultCursor(); } else { blankCursor = Toolkit.getDefaultToolkit().createCustomCursor(cursorImg, new Point(0, 0), "blank cursor"); } } @Override synchronized public void paint(Graphics gfx) { super.paint(gfx); if (tweakMode && handles != null) { int currentTab = getCurrentCodeIndex(); // enable anti-aliasing Graphics2D g2d = (Graphics2D)gfx; g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); for (Handle n : handles.get(currentTab)) { // update n position and width, and draw it int lineStartChar = textArea.getLineStartOffset(n.line); int x = textArea.offsetToX(n.line, n.newStartChar - lineStartChar); int y = textArea.lineToY(n.line) + fm.getHeight() + 1; int end = textArea.offsetToX(n.line, n.newEndChar - lineStartChar); n.setPos(x, y); n.setWidth(end - x); n.draw(g2d, n==mouseHandle); } // draw color boxes for (ColorControlBox cBox: colorBoxes.get(currentTab)) { int lineStartChar = textArea.getLineStartOffset(cBox.getLine()); int x = textArea.offsetToX(cBox.getLine(), cBox.getCharIndex() - lineStartChar); int y = textArea.lineToY(cBox.getLine()) + fm.getDescent(); cBox.setPos(x, y+1); cBox.draw(g2d); } } } protected void startTweakMode() { addMouseListener(new MouseListener() { @Override public void mouseReleased(MouseEvent e) { if (mouseHandle != null) { mouseHandle.resetProgress(); mouseHandle = null; updateCursor(e.getX(), e.getY()); repaint(); } } @Override public void mousePressed(MouseEvent e) { int currentTab = getCurrentCodeIndex(); // check for clicks on number handles for (Handle n : handles.get(currentTab)) { if (n.pick(e.getX(), e.getY())) { cursorType = -1; JavaTextAreaPainter.this.setCursor(blankCursor); mouseHandle = n; mouseHandle.setCenterX(e.getX()); repaint(); return; } } // check for clicks on color boxes for (ColorControlBox box : colorBoxes.get(currentTab)) { if (box.pick(e.getX(), e.getY())) { if (colorSelector != null) { // we already show a color selector, close it colorSelector.frame.dispatchEvent(new WindowEvent(colorSelector.frame, WindowEvent.WINDOW_CLOSING)); } colorSelector = new ColorSelector(box); colorSelector.frame.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { colorSelector.frame.setVisible(false); colorSelector = null; } }); colorSelector.show(getLocationOnScreen().x + e.getX() + 30, getLocationOnScreen().y + e.getY() - 130); } } } @Override public void mouseExited(MouseEvent e) { } @Override public void mouseEntered(MouseEvent e) { } @Override public void mouseClicked(MouseEvent e) { } }); addMouseMotionListener(new MouseMotionListener() { @Override public void mouseMoved(MouseEvent e) { updateCursor(e.getX(), e.getY()); if (!Settings.alwaysShowColorBoxes) { showHideColorBoxes(e.getY()); } } @Override public void mouseDragged(MouseEvent e) { if (mouseHandle != null) { // set the current drag amount of the arrows mouseHandle.setCurrentX(e.getX()); // update code text with the new value updateCodeText(); if (colorSelector != null) { colorSelector.refreshColor(); } repaint(); } } }); tweakMode = true; setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); repaint(); } protected void stopTweakMode() { tweakMode = false; if (colorSelector != null) { colorSelector.hide(); WindowEvent windowEvent = new WindowEvent(colorSelector.frame, WindowEvent.WINDOW_CLOSING); colorSelector.frame.dispatchEvent(windowEvent); } setCursor(new Cursor(Cursor.TEXT_CURSOR)); repaint(); } protected void updateTweakInterface(List<List<Handle>> handles, List<List<ColorControlBox>> colorBoxes) { this.handles = handles; this.colorBoxes = colorBoxes; updateTweakInterfacePositions(); repaint(); } /** * Initialize all the number changing interfaces. * synchronize this method to prevent the execution of 'paint' in the middle. * (don't paint while we make changes to the text of the editor) */ private synchronized void updateTweakInterfacePositions() { SketchCode[] code = getEditor().getSketch().getCode(); int prevScroll = textArea.getVerticalScrollPosition(); String prevText = textArea.getText(); for (int tab=0; tab<code.length; tab++) { String tabCode = getJavaEditor().baseCode[tab]; textArea.setText(tabCode); for (Handle n : handles.get(tab)) { int lineStartChar = textArea.getLineStartOffset(n.line); int x = textArea.offsetToX(n.line, n.newStartChar - lineStartChar); int end = textArea.offsetToX(n.line, n.newEndChar - lineStartChar); int y = textArea.lineToY(n.line) + fm.getHeight() + 1; n.initInterface(x, y, end-x, fm.getHeight()); } for (ColorControlBox cBox : colorBoxes.get(tab)) { int lineStartChar = textArea.getLineStartOffset(cBox.getLine()); int x = textArea.offsetToX(cBox.getLine(), cBox.getCharIndex() - lineStartChar); int y = textArea.lineToY(cBox.getLine()) + fm.getDescent(); cBox.initInterface(this, x, y+1, fm.getHeight()-2, fm.getHeight()-2); } } textArea.setText(prevText); textArea.scrollTo(prevScroll, 0); } /** * Take the saved code of the current tab and replace * all numbers with their current values. * Update TextArea with the new code. */ public void updateCodeText() { int charInc = 0; int currentTab = getCurrentCodeIndex(); SketchCode sc = getEditor().getSketch().getCode(currentTab); String code = getJavaEditor().baseCode[currentTab]; for (Handle n : handles.get(currentTab)) { int s = n.startChar + charInc; int e = n.endChar + charInc; code = replaceString(code, s, e, n.strNewValue); n.newStartChar = n.startChar + charInc; charInc += n.strNewValue.length() - n.strValue.length(); n.newEndChar = n.endChar + charInc; } replaceTextAreaCode(code); // update also the sketch code for later sc.setProgram(code); } // don't paint while we do the stuff below private synchronized void replaceTextAreaCode(String code) { // by default setText will scroll all the way to the end // remember current scroll position int scrollLine = textArea.getVerticalScrollPosition(); int scrollHor = textArea.getHorizontalScrollPosition(); textArea.setText(code); textArea.setOrigin(scrollLine, -scrollHor); } static private String replaceString(String str, int start, int end, String put) { return str.substring(0, start) + put + str.substring(end, str.length()); } private void updateCursor(int mouseX, int mouseY) { int currentTab = getCurrentCodeIndex(); for (Handle n : handles.get(currentTab)) { if (n.pick(mouseX, mouseY)) { cursorType = Cursor.W_RESIZE_CURSOR; setCursor(new Cursor(cursorType)); return; } } for (ColorControlBox colorBox : colorBoxes.get(currentTab)) { if (colorBox.pick(mouseX, mouseY)) { cursorType = Cursor.HAND_CURSOR; setCursor(new Cursor(cursorType)); return; } } if (cursorType == Cursor.W_RESIZE_CURSOR || cursorType == Cursor.HAND_CURSOR || cursorType == -1) { cursorType = Cursor.DEFAULT_CURSOR; setCursor(new Cursor(cursorType)); } } private void showHideColorBoxes(int y) { // display the box if the mouse if in the same line. // always keep the color box of the color selector. int currentTab = getCurrentCodeIndex(); boolean change = false; for (ColorControlBox box : colorBoxes.get(currentTab)) { if (box.setMouseY(y)) { change = true; } } if (colorSelector != null) { colorSelector.colorBox.visible = true; } if (change) { repaint(); } } // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . private JavaEditor getJavaEditor() { return (JavaEditor) getEditor(); } private int getCurrentCodeIndex() { return getEditor().getSketch().getCurrentCodeIndex(); } }