package processing.app.graph; import java.awt.Color; import java.awt.Cursor; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Point; import java.awt.Polygon; import java.awt.RenderingHints; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; import java.awt.event.ComponentListener; 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.MouseMotionListener; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.ActionMap; import javax.swing.InputMap; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JDesktopPane; import javax.swing.JInternalFrame; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.KeyStroke; import javax.swing.TransferHandler; import javax.swing.WindowConstants; import javax.swing.event.InternalFrameAdapter; import javax.swing.event.InternalFrameEvent; import javax.swing.event.InternalFrameListener; import processing.app.Base; import processing.app.DrawingArea; import processing.app.Editor; import processing.app.Preferences; import processing.app.syntax.JEditTextArea; import processing.app.syntax.PdeTextAreaDefaults; import processing.app.syntax.TextAreaDefaults; import processing.app.util.kConstants; import com.mxgraph.model.mxGraphModel; import com.mxgraph.model.mxICell; import com.mxgraph.swing.util.mxMouseControl; import com.mxgraph.view.mxCellState; //TODO at some future date: make right-click popup menus for code windows /* * For the nth time, stop trying to make this something * other than 3 internal frames */ public class kCodeWindow { private static final int TEXTAREA_HORIZ_OFFSET = 3; // model protected final String id; protected JDesktopPane desktop; // swing components protected JEditTextArea textarea; protected JInternalFrame editFrame; protected JInternalFrame triangleFrame; protected JInternalFrame buttonFrame; protected JButton closeButton; protected JButton moveButton; protected static final int TEXTAREA_DEFAULT_ROWS = 20; protected static final int TEXTAREA_DEFAULT_COLS = 200; protected static final int editFrame_DEFAULT_WIDTH = 150; protected static final int editFrame_DEFAULT_HEIGHT = 75; // triangle stuff protected String direction; protected static final int TRIANGLE_BASE = 15; protected static final int TRIANGLE_DEFAULT_HEIGHT = 25; private static final int BUTTON_ICON_HEIGHT = 15; //14 private static final int BUTTON_ICON_WIDTH = 15; //12 private static final int BUTTON_GAP = 3; //12 /** * Constructor #1 * * @param cell * @param desktop */ public kCodeWindow(mxICell cell, JDesktopPane desktop) { this(cell.getId(), ((kCellValue) cell.getValue()).getLabel(), desktop); } /** * Constructor: The one we're working on right now. * * @param id * @param label * @param desktop */ public kCodeWindow(String id, String label, JDesktopPane desktop) { this.id = id; this.desktop = desktop; // make the editor portions TextAreaDefaults editareaSettings = new PdeTextAreaDefaults(); editareaSettings.rows = TEXTAREA_DEFAULT_ROWS; editareaSettings.cols = TEXTAREA_DEFAULT_COLS; textarea = new JEditTextArea(editareaSettings); textarea.getDocument().setTokenMarker(Editor.pdeTokenMarker); textarea.setEventsEnabled(false); //suppress JEditTextArea events (not that anyone is listening to it) textarea.setEditable(true); textarea.setHorizontalOffset(TEXTAREA_HORIZ_OFFSET); textarea.getPainter().setLineHighlightEnabled(false); // else looks funny textarea.getPainter().setBackground(kConstants.CODE_WINDOW_COLOR); setShortcutKeystrokes(); JScrollPane scrollPane = new JScrollPane(textarea); scrollPane.setBorder(null); // scrollPane.setOpaque(true); editFrame = new JInternalFrame(label, true, false, false, false); editFrame.setContentPane(textarea); editFrame.setDefaultCloseOperation(WindowConstants.HIDE_ON_CLOSE); editFrame.setSize(editFrame_DEFAULT_WIDTH, editFrame_DEFAULT_HEIGHT); editFrame.setBorder(null); editFrame.setOpaque(true); // make the triangle triangleFrame = new JInternalFrame("", false, false, false, false); triangleFrame.setOpaque(false); triangleFrame.getRootPane().setBackground(new Color(0,0,0,0)); //needs this to actually do the trick triangleFrame.setContentPane(new Triangle("SE", 0, TRIANGLE_BASE, TRIANGLE_DEFAULT_HEIGHT, TRIANGLE_BASE)); triangleFrame.setDefaultCloseOperation(WindowConstants.HIDE_ON_CLOSE); triangleFrame.setSize(TRIANGLE_BASE, TRIANGLE_DEFAULT_HEIGHT); triangleFrame.setBorder(null); // remove the ability to move the triangle iframe MouseMotionListener[] actions = (MouseMotionListener[]) triangleFrame .getListeners(MouseMotionListener.class); for (int i = 0; i < actions.length; i++) triangleFrame.removeMouseMotionListener(actions[i]); // make the buttons moveButton = new JButton(Base.getImageIcon("codewindow-activ-move.gif", desktop)); moveButton.setDisabledIcon(Base.getImageIcon("codewindow-inact-move.gif", desktop)); moveButton.setVisible(true); moveButton.setBorder(null); moveButton.setOpaque(false); moveButton.setCursor(new Cursor(Cursor.HAND_CURSOR)); closeButton = new JButton(Base.getImageIcon("codewindow-activ-close.gif", desktop)); closeButton.setDisabledIcon(Base.getImageIcon("codewindow-inact-close.gif", desktop)); closeButton.setVisible(true); closeButton.setBorder(null); closeButton.setOpaque(false); closeButton.setCursor(new Cursor(Cursor.HAND_CURSOR)); JPanel buttonPanel = new JPanel(); buttonPanel.setLayout(new FlowLayout(FlowLayout.LEFT, 1, 0)); buttonPanel.setBorder(null); buttonPanel.setOpaque(false); buttonPanel.add(moveButton); buttonPanel.add(closeButton); buttonFrame = new JInternalFrame("", false, false, false, false); buttonFrame.setContentPane(buttonPanel); buttonFrame.setOpaque(false); buttonFrame.getRootPane().setBackground(new Color(0,0,0,0)); buttonFrame.setSize(BUTTON_ICON_WIDTH*2+BUTTON_GAP, BUTTON_ICON_HEIGHT); buttonFrame.setBorder(null); // myriad event handling installFocusHandlers(buttonPanel); // hide code window when escape key is hit textarea.addKeyListener(new KeyAdapter() { public void keyPressed(KeyEvent e) { if (e.getKeyCode() == KeyEvent.VK_ESCAPE) setVisible(false); } }); // add dragging function of the move button, // reset the triangle after mouse release, and shift the editFrame // along with the mouse when the user is moving the window mxMouseControl moveListener = createMoveListener(); moveButton.addMouseListener(moveListener); moveButton.addMouseMotionListener(moveListener); closeButton.addMouseListener(createCloseListener()); // listens to resizing of editFrame and adjusts the position of the // buttons and the shape of the triangle accordingly editFrame.addComponentListener(createResizeListener()); // when code windows are on top of each other, layers them correctly // such that when the user clicks on any part of a code window // all three component internal frames are brought to the top // so they appear "focused" also InternalFrameListener iframeListener = new InternalFrameAdapter() { public void internalFrameActivated(InternalFrameEvent e) { moveToFrontLayer(); } public void internalFrameDeactivated(InternalFrameEvent e) { moveToBackLayer(); } }; editFrame.addInternalFrameListener(iframeListener); buttonFrame.addInternalFrameListener(iframeListener); triangleFrame.addInternalFrameListener(iframeListener); // add everything to desktop desktop.add(editFrame); desktop.add(buttonFrame); desktop.add(triangleFrame); moveToBackLayer(); } /** * @param buttonPanel */ protected void installFocusHandlers(JPanel buttonPanel) { // disable buttons when this "frame" is defocused FocusListener focusListener = new FocusListener() { public void focusGained(FocusEvent e) { moveButton.setEnabled(true); closeButton.setEnabled(true); } public void focusLost(FocusEvent e) { moveButton.setEnabled(false); closeButton.setEnabled(false); } }; textarea.addFocusListener(focusListener); //have to put the focus listener on every component else it doesn't work every time buttonPanel.addFocusListener(focusListener); moveButton.addFocusListener(focusListener); closeButton.addFocusListener(focusListener); } /** * Add close function for the close button because of focus issues the * normal ActionPerformed route requires too many clicks. */ protected MouseAdapter createCloseListener() { return new MouseAdapter() { public void mouseReleased(MouseEvent e) { // System.out.println("kCW >> mouse clicked on close window"); setVisible(false); } }; } /** * Add dragging function of the move button, reset the triangle after mouse * release, and shift the editFrame along with the mouse when the user is * moving the window * * @return */ protected mxMouseControl createMoveListener() { return new mxMouseControl() { public void mouseDragged(MouseEvent e) { Point buttonLocation = buttonFrame.getLocation(); Point realLocation = new Point(e.getX() + buttonLocation.x, e.getY() + buttonLocation.y); Dimension editFrameSize = editFrame.getSize(); triangleFrame.setVisible(false); editFrame.setLocation(e.getX(), e.getY()); setFrameGeometry("SE", 0, realLocation.x - editFrameSize.width + buttonFrame.getWidth()*3/4, realLocation.y - buttonFrame.getHeight()); } public void mouseReleased(MouseEvent e) { updateTriangle(e.getX() + buttonFrame.getLocation().x, e.getY() + buttonFrame.getLocation().y); triangleFrame.setVisible(true); } }; } /** * Listens to resizing of editFrame and adjusts the position of the buttons * and the shape of the triangle accordingly * * @author susiefu * @return */ protected ComponentListener createResizeListener() { return new ComponentAdapter() { public void componentResized(ComponentEvent e) { moveButtons(); moveTriangle(); } private void moveButtons() { Point windowloc = new Point(editFrame.getLocation()); Dimension windowsize = new Dimension(editFrame.getSize()); buttonFrame.setLocation(windowloc.x + windowsize.width - buttonFrame.getWidth() + 1, windowloc.y - buttonFrame.getHeight()); } /** * Moves the triangle according to the size of the editFrame */ private void moveTriangle() { Point windowloc = new Point(editFrame.getLocation()); Dimension windowsize = editFrame.getSize(); int X_FUDGE = 15; int Y_FUDGE = 5; int tribase = (Math.round((Math .min(windowsize.width, windowsize.height))) / 10 > 15) ? Math .round((Math.min(windowsize.width, windowsize.height))) / 10 : 15; // How // wide // the // base // of // the // triangle // is int tilt = -1; int cellX = getCellCenter().x; int cellY = getCellCenter().y; if (direction == "SE") { // Right , bottom (DEFAULT) // DO NOTHING } else if (direction == "S") { // Middle, bottom // DO NOTHING } else if (direction == "W") { // Left, middle if (windowloc.x + windowsize.width > cellX) { direction = "NONE"; } else { int height; int width = Math.abs(windowloc.x + windowsize.width - cellX); if (windowloc.y >= cellY) { tilt = 0; // Pointing up height = Math.abs(windowloc.y - cellY) + tribase; } else { tilt = 1; // Pointing down if (Math.abs(windowloc.y - cellY) > tribase) { height = Math.abs(windowloc.y - cellY); } else { height = tribase; } } triangleFrame.setSize(width, height); ((Triangle) triangleFrame.getContentPane()).setGeometry(direction, tilt, width, height, tribase); triangleFrame.setLocation(windowloc.x + windowsize.width, windowloc.y); triangleFrame.setVisible(true); } } else if (direction == "NW") { // Left, top if (windowloc.y + windowsize.height > cellY) { direction = "NONE"; } else { int height = Math.abs(windowloc.y + windowsize.height - cellY); int width; if (windowloc.x + windowsize.width > cellX) { tilt = 0; // Pointing left width = Math.abs(windowloc.x + windowsize.width - cellX); } else { tilt = 1; // Pointing right width = Math.abs(windowloc.x + windowsize.width - cellX) + tribase; } triangleFrame.setSize(width, height); ((Triangle) triangleFrame.getContentPane()).setGeometry(direction, tilt, width, height, tribase); if (tilt == 0) { triangleFrame.setLocation(windowloc.x + windowsize.width - width, windowloc.y + windowsize.height); } else { triangleFrame.setLocation(windowloc.x + windowsize.width - tribase, windowloc.y + windowsize.height); } triangleFrame.setVisible(true); } } else if (direction == "N") { // Middle, top if (windowloc.y + windowsize.height > cellY) { direction = "NONE"; } else { int height = Math.abs(windowloc.y + windowsize.height - cellY); int width; if (windowloc.x >= cellX) { tilt = 0; // Pointing left width = Math.abs(windowloc.x - cellX) + tribase; } else { tilt = 1; // Pointing right if (Math.abs(windowloc.x - cellX) > tribase) { width = Math.abs(windowloc.x - cellX); } else { width = tribase; } } triangleFrame.setSize(width, height); ((Triangle) triangleFrame.getContentPane()).setGeometry(direction, tilt, width, height, tribase); if (tilt == 0) { // There is a fudge factor of 2 b/c of the space between the // border of the editFrame and the line that shows the border triangleFrame.setLocation(windowloc.x + tribase - width, windowloc.y + windowsize.height - 2); } else { // There is a fudge factor of 2 b/c of the space between the // border of the editFrame and the line that shows the border triangleFrame.setLocation(windowloc.x, windowloc.y + windowsize.height - 2); } triangleFrame.setVisible(true); } } else if (direction == "E") { // Right, middle // DO NOTHING } else { // DIRECTION = "NONE" triangleFrame.setVisible(false); if (windowloc.x + windowsize.width / 2 >= cellX && windowloc.y >= cellY + Y_FUDGE) { // Right , bottom (DEFAULT) direction = "SE"; } else if (windowloc.x + windowsize.width / 2 < cellX && windowloc.x + windowsize.width > cellX && windowloc.y >= cellY + Y_FUDGE) { // Middle, bottom direction = "S"; // SAME CODE AS "SE" DIRECTION BECAUSE OF // OVERLAPPING OF BUTTONS AND TRIANGLE } else if (windowloc.x + windowsize.width <= cellX && windowloc.y + windowsize.height + 10 * Y_FUDGE >= cellY) { // Left, // middle direction = "W"; } else if (windowloc.x + windowsize.width / 2 <= cellX && windowloc.y + windowsize.height + 10 * Y_FUDGE < cellY) { // Left, // top direction = "NW"; } else if (windowloc.x + windowsize.width / 2 > cellX && windowloc.x < cellX + X_FUDGE && windowloc.y + windowsize.height + Y_FUDGE < cellY) { // Middle, // top direction = "N"; } else if (windowloc.x >= cellX + X_FUDGE && windowloc.y - Y_FUDGE < cellY) { // Right, middle direction = "E"; } else { direction = "NONE"; } } } public void componentShown(ComponentEvent e) { triangleFrame.setVisible(true); } }; } /** * To support drag-drop of strings, and cut-copy-paste actions in the code window text editor. */ protected void setShortcutKeystrokes() { final int SHORTCUT_KEY_MASK = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(); InputMap imap = textarea.getInputMap(); imap.put(KeyStroke.getKeyStroke('X', SHORTCUT_KEY_MASK), TransferHandler.getCutAction().getValue(Action.NAME)); imap.put(KeyStroke.getKeyStroke('C', SHORTCUT_KEY_MASK), TransferHandler.getCopyAction().getValue(Action.NAME)); imap.put(KeyStroke.getKeyStroke('V', SHORTCUT_KEY_MASK), TransferHandler.getPasteAction().getValue(Action.NAME)); imap.put(KeyStroke.getKeyStroke('A', SHORTCUT_KEY_MASK), "selectAll"); imap.put(KeyStroke.getKeyStroke('/', SHORTCUT_KEY_MASK), "commentUncomment"); imap.put(KeyStroke.getKeyStroke(']', SHORTCUT_KEY_MASK), "increaseIndent"); imap.put(KeyStroke.getKeyStroke('[', SHORTCUT_KEY_MASK), "decreaseIndent"); ActionMap amap = textarea.getActionMap(); amap.put(TransferHandler.getCutAction().getValue(Action.NAME), new AbstractAction() { public void actionPerformed(ActionEvent e) { // System.out.println("kCodeWindow ActionMap >> cut "+e.getSource()); ((JEditTextArea) e.getSource()).cut(); } }); amap.put(TransferHandler.getCopyAction().getValue(Action.NAME), new AbstractAction() { public void actionPerformed(ActionEvent e) { // System.out.println("kCodeWindow ActionMap >> copy "+e.getSource()); ((JEditTextArea) e.getSource()).copy(); } }); amap.put(TransferHandler.getPasteAction().getValue(Action.NAME), new AbstractAction() { public void actionPerformed(ActionEvent e) { // System.out.println("kCodeWindow ActionMap >> paste "+e.getSource()); ((JEditTextArea) e.getSource()).paste(); } }); amap.put("selectAll", new AbstractAction() { public void actionPerformed(ActionEvent e) { // System.out.println("kCodeWindow ActionMap >> select all "+e.getSource()); ((JEditTextArea) e.getSource()).selectAll(); } }); amap.put("commentUncomment", new AbstractAction() { public void actionPerformed(ActionEvent e) { // System.out.println("kCodeWindow ActionMap >> comment uncomment"+e.getSource()); handleCommentUncomment(); } }); amap.put("increaseIndent", new AbstractAction() { public void actionPerformed(ActionEvent e) { // System.out.println("kCodeWindow ActionMap >> increaseIndent"+e.getSource()); handleIndentOutdent(true); } }); amap.put("decreaseIndent", new AbstractAction() { public void actionPerformed(ActionEvent e) { // System.out.println("kCodeWindow ActionMap >> decreaseIndent"+e.getSource()); handleIndentOutdent(false); } }); } /** * @see processing.app.Editor#handleCommentUncomment * @author fry */ public void handleCommentUncomment() { //TODO startCompoundEdit(); int startLine = textarea.getSelectionStartLine(); int stopLine = textarea.getSelectionStopLine(); int lastLineStart = textarea.getLineStartOffset(stopLine); int selectionStop = textarea.getSelectionStop(); // If the selection ends at the beginning of the last line, // then don't (un)comment that line. if (selectionStop == lastLineStart) { // Though if there's no selection, don't do that if (textarea.isSelectionActive()) { stopLine--; } } // If the text is empty, ignore the user. // Also ensure that all lines are commented (not just the first) // when determining whether to comment or uncomment. int length = textarea.getDocumentLength(); boolean commented = true; for (int i = startLine; commented && (i <= stopLine); i++) { int pos = textarea.getLineStartOffset(i); if (pos + 2 > length) { commented = false; } else { // Check the first two characters to see if it's already a comment. String begin = textarea.getText(pos, 2); //System.out.println("begin is '" + begin + "'"); commented = begin.equals("//"); } } for (int line = startLine; line <= stopLine; line++) { int location = textarea.getLineStartOffset(line); if (commented) { // remove a comment textarea.select(location, location+2); if (textarea.getSelectedText().equals("//")) { textarea.setSelectedText(""); //pseudo-code: //find open code windows //find localLocation //insert/remove //update all codeblocks after } } else { // add a comment textarea.select(location, location); textarea.setSelectedText("//"); } } // Subtract one from the end, otherwise selects past the current line. // (Which causes subsequent calls to keep expanding the selection) textarea.select(textarea.getLineStartOffset(startLine), textarea.getLineStopOffset(stopLine) - 1); // stopCompoundEdit(); } /** * @see processing.app.Editor#handleIndentOutdent * @author fry */ public void handleIndentOutdent(boolean indent) { int tabSize = Preferences.getInteger("editor.tabs.size"); String tabString = " ".substring(0, tabSize); // TODO startCompoundEdit(); int startLine = textarea.getSelectionStartLine(); int stopLine = textarea.getSelectionStopLine(); // If the selection ends at the beginning of the last line, // then don't (un)comment that line. int lastLineStart = textarea.getLineStartOffset(stopLine); int selectionStop = textarea.getSelectionStop(); if (selectionStop == lastLineStart) { // Though if there's no selection, don't do that if (textarea.isSelectionActive()) { stopLine--; } } for (int line = startLine; line <= stopLine; line++) { int location = textarea.getLineStartOffset(line); if (indent) { textarea.select(location, location); textarea.setSelectedText(tabString); } else { // outdent textarea.select(location, location + tabSize); // Don't eat code if it's not indented if (textarea.getSelectedText().equals(tabString)) { textarea.setSelectedText(""); } } } // Subtract one from the end, otherwise selects past the current line. // (Which causes subsequent calls to keep expanding the selection) textarea.select(textarea.getLineStartOffset(startLine), textarea.getLineStopOffset(stopLine) - 1); // stopCompoundEdit(); } /** * Get the id of this code window (matches the id of its associated cell) * * @return */ public String getId() { return id; } /** * Return the text editor component; used by Editor to monitor code window * document updates * * @return */ public JEditTextArea getTextArea() { return textarea; } /** * Show the code window and all its component parts * @param b */ public void setVisible(boolean b) { // System.out.println("kCodeWindow opaque >> editFrame="+editFrame.isOpaque()+ " buttonFrame="+buttonFrame.isOpaque()+" triangleFrame="+triangleFrame.isOpaque()); editFrame.setVisible(b); buttonFrame.setVisible(b); triangleFrame.setVisible(b); ((DrawingArea) desktop).fireCodeWindowEvent(); if (b) textarea.requestFocus(); } /** * Returns if the code window (represented by its textarea) is visible * @param b */ public boolean isVisible() { return editFrame.isVisible(); } /** * Fills in default direction and tilt for a code * window popping out of x,y location. * * @param x * @param y */ public void setLocation(int x, int y) { resetTriangleToDefault(); setFrameGeometry("NONE", 0, x, y); } /** * Will update the triangle assuming that the cell center has moved but * that the edit window and button panel has not. * @author achang */ public void updateTriangle() { Point buttonLocation = buttonFrame.getLocation(); updateTriangle(buttonFrame.getWidth()/4 + buttonLocation.x, buttonFrame.getHeight()/2 + buttonLocation.y); } /** * Will update the triangle given the absolute location of the center of * the moveButton. * @author achang, adapted from code by susiefu */ public void updateTriangle(int x, int y) { Point realLocation = new Point(x,y); Dimension editFrameSize = editFrame.getSize(); Point editFrameLocation = editFrame.getLocation(); // new Point(realLocation.x - editFrameSize.width + buttonFrame.getWidth()*3/4, // realLocation.y + buttonFrame.getHeight()/2); int X_FUDGE = 15; int Y_FUDGE = 5; int tribase = (Math.round((Math.min(editFrameSize.width, editFrameSize.height))) / 10 > TRIANGLE_BASE) ? Math.round((Math.min(editFrameSize.width, editFrameSize.height))) / 10 : TRIANGLE_BASE; int tilt = -1; int cellX = getCellCenter().x; int cellY = getCellCenter().y; // System.out.println("kCW >> updateTriangle >> cellCenter="+getCellCenter()); int width, height; if (editFrameLocation.x + editFrameSize.width / 2 >= cellX && editFrameLocation.x - X_FUDGE <= cellX && editFrameLocation.y >= cellY) { // Right , bottom (DEFAULT) direction = "SE"; height = editFrameLocation.y - cellY; if (editFrameLocation.x >= cellX) { tilt = 0; // Pointing left width = Math.abs(editFrameLocation.x - cellX) + tribase; } else { tilt = 1; // Pointing right if (Math.abs(editFrameLocation.x - cellX) > tribase) { width = Math.abs(editFrameLocation.x - cellX); } else { width = tribase; } } } else if (editFrameLocation.x + editFrameSize.width / 2 < cellX && editFrameLocation.x + editFrameSize.width > cellX && editFrameLocation.y >= cellY + Y_FUDGE) { // Middle, // bottom direction = "S"; // SAME CODE AS "SE" DIRECTION BECAUSE OF OVERLAPPING // OF BUTTONS AND TRIANGLE height = editFrameLocation.y - cellY; if (editFrameLocation.x >= cellX) { tilt = 0; // Pointing left width = Math.abs(editFrameLocation.x - cellX) + tribase; } else { tilt = 1; // Pointing right if (Math.abs(editFrameLocation.x - cellX) > tribase) { width = Math.abs(editFrameLocation.x - cellX); } else { width = tribase; } } } else if (editFrameLocation.x + editFrameSize.width <= cellX && editFrameLocation.y + editFrameSize.height + 3 * Y_FUDGE >= cellY || editFrameLocation.x + 3 * editFrameSize.width / 2 < cellX && editFrameLocation.y + editFrameSize.height < cellY ) { // Left, middle direction = "W"; width = Math.abs(editFrameLocation.x + editFrameSize.width - cellX); if (editFrameLocation.y >= cellY) { tilt = 0; // Pointing up height = Math.abs(editFrameLocation.y - cellY) + tribase; } else { tilt = 1; // Pointing down if (Math.abs(editFrameLocation.y - cellY) > tribase) { height = Math.abs(editFrameLocation.y - cellY); } else { height = tribase; } } } else if (editFrameLocation.x + editFrameSize.width / 2 <= cellX && editFrameLocation.x + 3 * editFrameSize.width / 2 >= cellX && editFrameLocation.y + editFrameSize.height + 3 * Y_FUDGE < cellY) { // Left, // top direction = "NW"; height = Math.abs(editFrameLocation.y + editFrameSize.height - cellY); if (editFrameLocation.x + editFrameSize.width > cellX) { tilt = 0; // Pointing left if (Math.abs(editFrameLocation.x + editFrameSize.width - cellX) > tribase) { width = Math.abs(editFrameLocation.x + editFrameSize.width - cellX); } else { width = tribase; } } else { tilt = 1; // Pointing right width = Math .abs(editFrameLocation.x + editFrameSize.width - cellX) + tribase; } } else if (editFrameLocation.x + editFrameSize.width / 2 > cellX && editFrameLocation.x - editFrameSize.width / 2 <= cellX && editFrameLocation.y + editFrameSize.height + Y_FUDGE < cellY) { // Middle, // top direction = "N"; height = Math.abs(editFrameLocation.y + editFrameSize.height - cellY); if (editFrameLocation.x >= cellX) { tilt = 0; // Pointing left width = Math.abs(editFrameLocation.x - cellX) + tribase; } else { tilt = 1; // Pointing right if (Math.abs(editFrameLocation.x - cellX) > tribase) { width = Math.abs(editFrameLocation.x - cellX); } else { width = tribase; } } } else if (editFrameLocation.x - X_FUDGE > cellX || editFrameLocation.y - editFrameSize.height / 2 >= cellY && editFrameLocation.x - X_FUDGE <= cellX) {// Right, middle direction = "E"; width = Math.abs(editFrameLocation.x - cellX); if (editFrameLocation.y >= cellY) { tilt = 0; // Pointing up height = Math.abs(editFrameLocation.y - cellY) + tribase; } else { tilt = 1; // Pointing down if (Math.abs(editFrameLocation.y - cellY) > tribase) { height = Math.abs(editFrameLocation.y - cellY); } else { height = tribase; } } } else { direction = "NONE"; height = editFrameLocation.y - cellY; if (editFrameLocation.x >= cellX) { tilt = 0; width = Math.abs(editFrameLocation.x - cellX) + tribase; } else { tilt = 1; if (Math.abs(editFrameLocation.x - cellX) > tribase) { width = Math.abs(editFrameLocation.x - cellX); } else { width = tribase; } } } triangleFrame.setSize(width, height); ((Triangle) triangleFrame.getContentPane()).setGeometry(direction, tilt, width, height, tribase); setFrameGeometry(direction, tilt, realLocation.x - editFrameSize.width + buttonFrame.getWidth()*3/4, realLocation.y - buttonFrame.getWidth()/2); } /** * Gets the coordinate location of the center of this code window's associated * cell TODO if we ever have to refer to the cell one more time it'll make * sense to save a reference to it as a member variable instead of saving the id... * Updated to use the cell state so this still works when graph is scaled/panned * * @author achang */ protected Point getCellCenter() { Object cell = ((mxGraphModel) ((DrawingArea) desktop) .getGraphComponent().getGraph().getModel()).getCell(id); mxCellState state = ((DrawingArea) desktop).getGraphComponent().getGraph().getView().getState(cell); return new Point((int) state.getCenterX(), (int) state.getCenterY()); } /** * Move internal frames to front so it isn't covered by other code windows. * * @author susiefu */ protected void moveToFrontLayer() { editFrame.setLayer(2); buttonFrame.setLayer(2); triangleFrame.setLayer(2); editFrame.moveToFront(); triangleFrame.moveToFront(); buttonFrame.moveToFront(); } /** * Allow internal frames to go behind other frames (i.e. code windows). * * @author susiefu */ protected void moveToBackLayer() { editFrame.setLayer(1); buttonFrame.setLayer(1); triangleFrame.setLayer(1); editFrame.moveToFront(); triangleFrame.moveToFront(); buttonFrame.moveToFront(); } /** * Sets the location for the 3 internal frames * * @author susiefu * @param direction * @param tilt * @param x * @param y */ protected void setFrameGeometry(String direction, int tilt, int x, int y) { Dimension editwindowsize = editFrame.getSize(); Dimension trisize = triangleFrame.getSize(); int tribase = (Math.round((Math.min(editwindowsize.width, editwindowsize.height))) / 10 > 15) ? Math .round((Math .min( editwindowsize.width, editwindowsize.height))) / 10 : 15; // How // wide // the // base // of // the // triangle // is int fudge = 1; // For the different directions if (direction == "SE") { // Default location if (tilt == 0) { triangleFrame.setLocation(x + tribase - trisize.width, y + 24 - trisize.height + fudge); } else { triangleFrame.setLocation(x, y + 24 - trisize.height + fudge); } } else if (direction == "S") { // SAME AS "SE" DIRECTION if (tilt == 0) { triangleFrame.setLocation(x + tribase - trisize.width, y + 24 - trisize.height + fudge); } else { triangleFrame.setLocation(x, y + 24 - trisize.height + fudge); } } else if (direction == "W") { if (tilt == 0) { triangleFrame.setLocation(x + editwindowsize.width - fudge, y + 24 - trisize.height + tribase); } else { triangleFrame.setLocation(x + editwindowsize.width - fudge, y + 24); } } else if (direction == "NW") { if (tilt == 0) { triangleFrame.setLocation(x + editwindowsize.width - trisize.width, y + 24 + editwindowsize.height - 2 * fudge); } else { triangleFrame.setLocation(x + editwindowsize.width - tribase, y + 25 + editwindowsize.height - 2 * fudge); } } else if (direction == "N") { if (tilt == 0) { triangleFrame.setLocation(x - trisize.width + tribase, y + 24 + editwindowsize.height - 2 * fudge); } else { triangleFrame .setLocation(x, y + 24 + editwindowsize.height - 2 * fudge); } } else if (direction == "E") { if (tilt == 0) { // Pointing up triangleFrame.setLocation(x - trisize.width + fudge, y + 24 - trisize.height + tribase); } else { triangleFrame.setLocation(x - trisize.width + fudge, y + 24); } } else if (direction == "NONE") { if (tilt == 0) { triangleFrame.setLocation(x + tribase - trisize.width, y + 24 - trisize.height + fudge); } else { triangleFrame.setLocation(x, y + 24 - trisize.height + fudge); } } editFrame.setLocation(x, y + 24); buttonFrame.setLocation(x + editwindowsize.width - buttonFrame.getWidth()+fudge, y + buttonFrame.getHeight()/2+fudge*2); } /** * Resets the triangle graphic to the fixed default location * * @author susiefu */ protected void resetTriangleToDefault() { int tribase = (Math.round((Math.min(editFrame.getSize().width, editFrame .getSize().height))) / 10 > 15) ? Math.round((Math.min(editFrame .getSize().width, editFrame.getSize().height))) / 10 : 15; // How wide // the base // of the // triangle // is triangleFrame.setSize(tribase, TRIANGLE_DEFAULT_HEIGHT); ((Triangle) triangleFrame.getContentPane()).setGeometry("SE", 0, tribase, TRIANGLE_DEFAULT_HEIGHT, tribase); } /** * Triangle graphic * * @author susiefu */ private class Triangle extends JComponent { private String triDirection; private int tilt, width, height, tribase; public Triangle(String direction, int tilt, int width, int height, int tribase) { setGeometry(direction, tilt, width, height, tribase); setOpaque(false); } public void setGeometry(String direction, int tilt, int width, int height, int tribase) { this.triDirection = direction; this.tilt = tilt; this.width = width; this.height = height; this.tribase = tribase; // How wide the base of the triangle is; } // Paints the triangle onto the iframe public void paintComponent(Graphics g) { super.paintComponent(g); // make pretty anti-aliased triangles Graphics2D g2 = (Graphics2D) g; g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON); Point p1, p2, p3; p1 = new Point(0, 0); p2 = new Point(width - tribase, height); p3 = new Point(width, height); if (triDirection == "SE" || triDirection == "S") { if (tilt == 0) { p1 = new Point(0, 0); p2 = new Point(width - tribase, height); p3 = new Point(width, height); } else if (tilt == 1) { p1 = new Point(width - 1, 0); p2 = new Point(0, height); p3 = new Point(tribase - 1, height); } } else if (triDirection == "W") { if (tilt == 0) { p1 = new Point(width, 0); p2 = new Point(0, height - tribase); p3 = new Point(0, height); } else if (tilt == 1) { p1 = new Point(width, height - 1); p2 = new Point(0, 0); p3 = new Point(0, tribase - 1); } } else if (triDirection == "NW" || triDirection == "N") { if (tilt == 0) { // Left tilt p1 = new Point(0, height); p2 = new Point(width, 0); p3 = new Point(width - tribase, 0); } else if (tilt == 1) { // Right tilt p1 = new Point(width - 1, height); p2 = new Point(tribase - 1, 0); p3 = new Point(0, 0); } } else if (triDirection == "E") { if (tilt == 0) { p1 = new Point(0, 0); p2 = new Point(width, height - tribase); p3 = new Point(width, height); } else if (tilt == 1) { p1 = new Point(0, height - 1); p2 = new Point(width, 0); p3 = new Point(width, tribase - 1); } } else { p1 = new Point(0, 0); p2 = new Point(0, TRIANGLE_DEFAULT_HEIGHT); p3 = new Point(tribase, TRIANGLE_DEFAULT_HEIGHT); } int[] xs = { p1.x, p2.x, p3.x }; int[] ys = { p1.y, p2.y, p3.y }; Polygon triangle = new Polygon(xs, ys, xs.length); g2.setColor(kConstants.CODE_WINDOW_COLOR); g2.fillPolygon(triangle); // g.setColor(Color.LIGHT_GRAY); // g.drawLine(p1.x,p1.y, p2.x, p2.y); // g.drawLine(p3.x,p3.y, p1.x, p1.y); } } }