/******************************************************************************** * * * (c) Copyright 2010 Verizon Communications USA and The Open University UK * * * * This software is freely distributed in accordance with * * the GNU Lesser General Public (LGPL) license, version 3 or later * * as published by the Free Software Foundation. * * For details see LGPL: http://www.fsf.org/licensing/licenses/lgpl.html * * and GPL: http://www.fsf.org/licensing/licenses/gpl-3.0.html * * * * This software is provided by the copyright holders and contributors "as is" * * and any express or implied warranties, including, but not limited to, the * * implied warranties of merchantability and fitness for a particular purpose * * are disclaimed. In no event shall the copyright owner or contributors be * * liable for any direct, indirect, incidental, special, exemplary, or * * consequential damages (including, but not limited to, procurement of * * substitute goods or services; loss of use, data, or profits; or business * * interruption) however caused and on any theory of liability, whether in * * contract, strict liability, or tort (including negligence or otherwise) * * arising in any way out of the use of this software, even if advised of the * * possibility of such damage. * * * ********************************************************************************/ package com.compendium.ui.plaf; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Component; import java.awt.Container; import java.awt.Dimension; import java.awt.Event; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Insets; import java.awt.Point; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.Stroke; import java.awt.Toolkit; import java.awt.datatransfer.Clipboard; import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.StringSelection; import java.awt.datatransfer.Transferable; import java.awt.event.KeyEvent; import java.awt.event.MouseEvent; import java.awt.geom.AffineTransform; import java.awt.geom.GeneralPath; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.Vector; import javax.swing.JComponent; import javax.swing.SwingUtilities; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.basic.BasicGraphicsUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.compendium.ProjectCompendium; import com.compendium.core.ICoreConstants; import com.compendium.core.datamodel.LinkProperties; import com.compendium.core.datamodel.Model; import com.compendium.ui.IUIConstants; import com.compendium.ui.UILine; import com.compendium.ui.UILink; import com.compendium.ui.UINode; import com.compendium.ui.UIViewPane; /** * The UI class for the UILink Component * * @author Mohammed Sajid Ali / Michelle Bachler */ public class LinkUI extends LineUI implements PropertyChangeListener{ /** * class's own logger */ final Logger log = LoggerFactory.getLogger(getClass()); /** the tolerance for mouse near line ***/ private static final int LINE_TOLERANCE = 3; /** The default stroke */ private static final BasicStroke SIMPLE_STROKE = new BasicStroke(1); /** the colour to use for a selected node.*/ private static final Color SELECTED_COLOR = Color.yellow; /** The colour to use for selected text in the node label.*/ private static final Color SELECTED_TEXT_COLOR = Color.black; /** The colour to use for the label background when editing a node label.*/ private static final Color EDITING_COLOR = Color.white; /** The colour to use for the label background when text is selected.*/ private static final Color TSELECTED_COLOR = Color.blue; /** the colour to use for the text when text is selected.*/ private static final Color TSELECTED_TEXT_COLOR = Color.white; /** The colour to use for the label border when the link label is rolled over with the mouse.*/ private static final Color ROLLOVER_COLOR = Color.cyan; /** The length of the text box when there is no text in it yet - big enough to get started with.*/ private static final int DEFAULT_TEXT_LENGTH = 30; /** The UILink associated with this LinkUI instance.*/ protected UILink oLink; /** The UIViewPane of the link associated with this LinkUI instance.*/ protected UIViewPane oViewPane; private Rectangle linkTypeRec = new Rectangle(); /** The PropertyChangeListener registered for this list.*/ private PropertyChangeListener oPropertyChangeListener; /** * Holds the list of the <code>TextRowElement</code> objects * with information on each row of text in the link label. */ private Vector textRowElements = null; /** Used to hold the calculated maximum width required to paint this link.*/ private int maxTextWidth = -1; /** The max number of characters to text against when painting.*/ private int maxCharWidth = -1; /** Used to hold the calculated maximum height required to paint this link's text area.*/ private int textHeight = -1; /** Used to hold the calculated maximum height required to paint this link.*/ private Dimension linkDimension = null; /** Defines whether dragging is started with right mouse button*/ private boolean bDragging = false; /** The clipboard instance used when performing cut/copy/paste actions in the link label.*/ private Clipboard clipboard = null; /** The are for the caret in the link label.*/ private Rectangle caretRectangle = null; /** The area for the node label.*/ private Rectangle labelRectangle = null; /** The current position of the caret in the link label.*/ private int currentCaretPosition = 0; /** The previous contents of the label for undoind a cut/copy/paste to one level.*/ private String previousString = ""; //$NON-NLS-1$ /** Indocates if the node label is currently being edited - Is the link in edit mode?*/ private boolean editing = false; /** Indicates if a double-click was performed in the link label (for selecting a word).*/ private boolean doubleClicked = false; /** The x position the mouse was clicked to start label editing or move row.*/ private int editX = 0; /** The y position the mouse was clicked to start label editing or move row.*/ private int editY = 0; /** The starting position for text selection in the link label.*/ private int startSelection = -1; /** The stopping position for text selection in the link label.*/ private int stopSelection = -1; /** The current row the cursor is in, in the link label.*/ private int currentRow = 0; /** Was the up key pressed to move the cursor up a row in the link label?*/ private boolean caretUp = false; /** Was the down key pressed to move the cursor down a row in the link label?*/ private boolean caretDown = false; /** Holds the last key pressed on this node.*/ private String sKeyPressed = ""; //$NON-NLS-1$ /** The shortcut key for the current platfrom.*/ private int shortcutKey; /** For calculating rollover/selection when mouse moves/clicked*/ private boolean isCurved = false; private boolean isSquared = false; private Rectangle oRectOne = null; private Rectangle oRectTwo = null; private Rectangle oRectThree = null; private GeneralPath oLinkPath = null; private int nThickness = 1; /** * Create a new LinkUI instance. * @param c, the component this is the ui to install for. */ public static ComponentUI createUI(JComponent c) { return new LinkUI(); } /** * Run any install instructions for installing this UI. * @param c, the component this is the ui for. */ public void installUI(JComponent c) { super.installUI(c); oLink = (UILink)c; oViewPane = oLink.getViewPane(); previousString = oLink.getText(); if (previousString != null) currentCaretPosition = previousString.length(); clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); shortcutKey = ProjectCompendium.APP.shortcutKey; oPropertyChangeListener = this; c.addPropertyChangeListener( oPropertyChangeListener ); } /** * Run any uninstall instructions for uninstalling this UI. * @param c, the component this is the ui to uninstall for. */ public void uninstallUI(JComponent c) { oLink = null; super.uninstallUI(c); if ( oPropertyChangeListener != null ) { c.removePropertyChangeListener( oPropertyChangeListener ); } oPropertyChangeListener = null; } /** * Handles a property chagne to the UILink. * @param evt, the associated PropertyChagenEvent object. */ public void propertyChange(PropertyChangeEvent evt) { String prop = evt.getPropertyName(); if (prop.equals(UILink.LABEL_PROPERTY)) { refreshBounds(); } else if (prop.equals(UILink.TYPE_PROPERTY)) { refreshBounds(); } //else if (prop.equals(UILine.ARROW_PROPERTY)) { // refreshBounds(); //} } /** * Delete the given UILink from the associated map. * @param uilink com.compendium.ui.UILink, the link to purge. */ public void purgeLink(UILink uilink) { oViewPane = uilink.getViewPane(); if (oViewPane != null) { UINode fromNode = uilink.getFromNode(); UINode toNode = uilink.getToNode(); try { //purge link from the database oViewPane.getView().purgeMemberLink(uilink.getLinkProperties()); // REMOVE LINK FROM DATA STRUCUTURE fromNode.removeLink(uilink); toNode.removeLink(uilink); oViewPane.remove(uilink); } catch(Exception ex) { log.info("Error: (LinkUI.purgeLink)\n\n"+ex.getLocalizedMessage()); //$NON-NLS-1$ } } } /** * Mark the given UILink as deleted from the associated map. * @param uilink com.compendium.ui.UILink, the link to delete. */ public void deleteLink(UILink uilink) { oViewPane = oLink.getViewPane(); UINode fromNode = uilink.getFromNode(); UINode toNode = uilink.getToNode(); try { //delete link in the datamodel layer oViewPane.getView().removeMemberLink(uilink.getLinkProperties()); fromNode.removeLink(uilink); toNode.removeLink(uilink); oViewPane.remove(uilink); } catch(Exception ex) { log.error("Error...", ex); ProjectCompendium.APP.displayError("LinkUI.deleteLink\n\n"+ex.getLocalizedMessage()); //$NON-NLS-1$ } } /** * Open the right-click popup menu associated with this LinkUI instance at the given coordinates. * @param x, the x position for the popup menu associated with this LinkUI instance. * @param y, the y position for the popup menu associated with this LinkUI instance. */ public void showPopupMenu(int x, int y) { oLink.showPopupMenu(this,x,y); } /** * Is this link currently being edited? * @return boolean, true if it is, else false. */ public boolean isEditing() { return editing; } /** * Set the link in editing mode. */ /*public void setEditing() { editing = true; startSelection = -1; stopSelection = -1; currentCaretPosition = oLink.getText().length(); doubleClicked = false; oLink.moveToFront(); oLink.requestFocus(); }*/ /** * Reset all the link label editing variables. */ public void resetEditing() { editing = false; currentCaretPosition = -1; editX=0; editY=0; bDragging = false; doubleClicked = false; startSelection = -1; stopSelection = -1; } /** * Convenience method to get the UILink by the popupmenu and other gui operations. * @return com.compendium.ui.UILink, the UILink associated with this LinkUI instance. */ public UILink getUILink() { return oLink; } /** * This class holds information about each row of the Link label. */ private class TextRowElement { String text = ""; //$NON-NLS-1$ int startPos = 0; Rectangle textR = null; boolean isRowWithCaret; public TextRowElement(String text, int startPos, Rectangle textR, boolean isRowWithCaret) { this.text = text; this.startPos = startPos; this.textR = textR; this.isRowWithCaret = isRowWithCaret; } public String getText() { return text; } public int getStartPosition() { return startPos; } public Rectangle getTextRect() { return textR; } public boolean getIsRowWithCaret() { return isRowWithCaret; } } /** * Draws the line on the given graphics context. * * @param g, the Graphics object for this pain method to use. * @param c, the component to paint. */ public void paint(Graphics g, JComponent c) { isCurved = false; isSquared = false; oRectOne = null; oRectTwo = null; oRectThree= null; UILink link = null; if (c instanceof UILink) link = (UILink)c; else return; LinkProperties props = link.getLinkProperties(); // get from and to points Point from = link.getFrom(); Point to = link.getTo(); // if one of the points is missing don't draw line if (from == null || to == null) return; // determine relative to and from points Point originalFrom = new Point(); Point originalTo = new Point(); if (oLink.getCoordinateType() == UILine.RELATIVE) { // coordinates already relative to this components coordinate system originalFrom = from; originalTo = to; } else { //always uses this one - is it loosing accuracy? // calculate the relative coordinates by converting the coordinates from // the parents coordinate system to this components coordinate system Container parent = link.getParent(); if (parent != null) { //g.setClip(parent.getX(), // parent.getY(), // parent.getWidth(), // parent.getHeight()); originalFrom = SwingUtilities.convertPoint(parent, from, link); originalTo = SwingUtilities.convertPoint(parent, to, link); } else { return; } } // set color of line if (link.isSelected()) g.setColor(link.getSelectedColor()); else g.setColor(link.getForeground()); Graphics2D g2d = (Graphics2D)g; nThickness = link.getCurrentLineThickness(); Stroke drawingStroke = new BasicStroke(nThickness); if (props != null && props.getLinkDashed() == ICoreConstants.LARGE_DASHED_LINE) { drawingStroke = new BasicStroke(nThickness, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0, new float[]{9}, 0); } else if (props != null && props.getLinkDashed() == ICoreConstants.SMALL_DASHED_LINE) { drawingStroke = new BasicStroke(nThickness, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0, new float[]{4}, 0); } g2d.setStroke(drawingStroke); //Line2D line = new Line2D.Double(20, 40, 100, 40); int linkStyle = ICoreConstants.STRAIGHT_LINK; if (props != null) { linkStyle = props.getLinkStyle(); } // Start Code by Corsaire - modified by MB int arrowOrientation = LineUI.ARROW_ORIENTATION_FREE; boolean isReallyNonLinear = linkStyle > ICoreConstants.STRAIGHT_LINK; int deltaX = originalTo.x - originalFrom.x; int deltaY = originalTo.y - originalFrom.y; if ((Math.abs(deltaX) < 20) || (Math.abs(deltaY) < 20)) { isReallyNonLinear = false; //under 10 pixels, �linear� mode is always used } g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); Point arrowFrom = new Point(originalFrom); Point arrowTo = new Point(originalTo); if (isReallyNonLinear) { // Middle point Point middle = new Point((originalFrom.x + originalTo.x)/2, (originalFrom.y + originalTo.y)/2); double angle = Math.acos(deltaY/deltaX); int x1 = 0, y1 = 0, x2 = 0, y2 = 0, x3 = 0, y3 = 0, x4 = 0, y4 = 0; if (Math.abs(angle) < 0.5) { arrowOrientation = LineUI.ARROW_ORIENTATION_VERTICAL; x1 = originalFrom.x; y1 = originalFrom.y; x2 = originalFrom.x; y2 = middle.y; x3 = originalTo.x; y3 = middle.y; x4 = originalTo.x; y4 = originalTo.y; } else { arrowOrientation = LineUI.ARROW_ORIENTATION_HORIZONTAL; x1 = originalFrom.x; y1 = originalFrom.y; x2 = middle.x; y2 = originalFrom.y; x3 = middle.x; y3 = originalTo.y; x4 = originalTo.x; y4 = originalTo.y; } // Temporary fix to reduce the length of the line // so it does not show outside the arrow head when thickness of line increased // Need a more elegant solution Point newTo = new Point(x4,y4); Point newFrom = new Point(x1,y1); switch (link.getArrow()) { case ICoreConstants.NO_ARROW: { break; } case ICoreConstants.ARROW_TO: { newTo = reduceLine(new Point(x3, y3), new Point(x4, y4), nThickness); newFrom = reduceLine(new Point(x2, y2), new Point(x1, y1), nThickness/2); break; } case ICoreConstants.ARROW_FROM: { newTo = reduceLine(new Point(x3, y3), new Point(x4, y4), nThickness/2); newFrom = reduceLine(new Point(x2, y2), new Point(x1, y1), nThickness); break; } case ICoreConstants.ARROW_TO_AND_FROM: { newTo = reduceLine(new Point(x3, y3), new Point(x4, y4), nThickness); newFrom = reduceLine(new Point(x2, y2), new Point(x1, y1), nThickness); break; } } x4 = newTo.x; y4 = newTo.y; x1 = newFrom.x; y1 = newFrom.y; if (linkStyle == ICoreConstants.CURVED_LINK) { isCurved = true; // compute the 'spline' path from the 4 given points oLinkPath = new GeneralPath(); oLinkPath.moveTo(x1, y1); oLinkPath.curveTo(x2, y2, x3, y3, x4, y4); g2d.draw(oLinkPath); } else if (linkStyle == ICoreConstants.SQUARE_LINK) { isSquared = true; int widthOne=0; int heightOne=0; if (x1 < x2) widthOne = x2-x1; else widthOne=x1-x2; if (y1 < y2) heightOne = y2-y1; else heightOne=y1-y2; int widthTwo=0; int heightTwo=0; if (x2 < x3) widthTwo = x3-x2; else widthTwo=x2-x3; if (y2 < y3) heightTwo = y3-y2; else heightTwo=y2-y3; int widthThree=0; int heightThree=0; if (x3 < x4) widthThree = x4-x3; else widthThree=x3-x4; if (y3 < y4) heightThree = y4-y3; else heightThree=y3-y4; int tol = (nThickness/2)+LINE_TOLERANCE; oRectOne = new Rectangle(x2-tol, y2-tol, widthOne+tol, heightOne+tol); oRectTwo = new Rectangle(x3-tol, y3-tol, widthTwo+tol, heightTwo+tol); oRectThree = new Rectangle(x4-tol, y4-tol, widthThree+tol, heightThree+tol); g.drawLine(x1, y1, x2, y2); g.drawLine(x2, y2, x3, y3); g.drawLine(x3, y3, x4, y4); } } else { // Temporary fix to reduce the length of the line // so it does not show outside the arrow head when thickness of line increased // Need a more elegant solution switch (link.getArrow()) { case ICoreConstants.NO_ARROW: { break; } case ICoreConstants.ARROW_TO: { originalTo = reduceLine(originalFrom, originalTo, nThickness); originalFrom = reduceLine(originalTo, originalFrom, nThickness/2); break; } case ICoreConstants.ARROW_FROM: { originalTo = reduceLine(originalFrom, originalTo, nThickness/2); originalFrom = reduceLine(originalTo, originalFrom, nThickness); break; } case ICoreConstants.ARROW_TO_AND_FROM: { originalTo = reduceLine(originalFrom, originalTo, nThickness); originalFrom = reduceLine(originalTo, originalFrom, nThickness); break; } } g.drawLine(originalFrom.x, originalFrom.y, originalTo.x, originalTo.y); } g2d.setStroke(SIMPLE_STROKE); // End Code by Corsaire // DRAW ARROW switch (link.getArrow()) { case ICoreConstants.NO_ARROW: { break; } case ICoreConstants.ARROW_TO: { //drawArrow((Graphics2D)g, arrowFrom.x, arrowFrom.y, arrowTo.x, arrowTo.y, new Float(link.getCurrentLineThickness())); drawArrow(g, arrowFrom, arrowTo, link.getCurrentArrowHeadWidth(), arrowOrientation, nThickness); break; } case ICoreConstants.ARROW_FROM: { //drawArrow((Graphics2D)g, arrowTo.x, arrowTo.y, arrowFrom.x, arrowFrom.y, new Float(link.getCurrentLineThickness())); drawArrow(g, arrowTo, arrowFrom, link.getCurrentArrowHeadWidth(), arrowOrientation, nThickness); break; } case ICoreConstants.ARROW_TO_AND_FROM: { drawArrow(g, arrowFrom, arrowTo, link.getCurrentArrowHeadWidth(), arrowOrientation, nThickness); drawArrow(g, arrowTo, arrowFrom, link.getCurrentArrowHeadWidth(), arrowOrientation, nThickness); //drawArrow((Graphics2D)g, arrowTo.x, arrowTo.y, arrowFrom.x, arrowFrom.y, new Float(link.getCurrentLineThickness())); //drawArrow((Graphics2D)g, arrowFrom.x, arrowFrom.y, arrowTo.x, arrowTo.y, new Float(link.getCurrentLineThickness())); break; } } drawTextArea(g, link); } /////////////////////////////////////////////////////////////////////////////////////////////////////////////// /** * Draw the link label. * * @param g, the Graphics object for this paint method to use. * @param c, the component to paint. */ private void drawTextArea(Graphics g, UILink link) { // IF THERE IS NO ROOM TO DRAW THE TEXT if (maxTextWidth <= 0 && textHeight <= 0) return; String text = link.getText(); Font font = link.getFont(); oViewPane = link.getViewPane(); AffineTransform trans=new AffineTransform(); trans.setToScale(oViewPane.getScale(), oViewPane.getScale()); Point p1 = new Point(font.getSize(), font.getSize()); try { p1 = (Point)trans.transform(p1, new Point(0, 0));} catch(Exception e) { log.info("can't convert font size (LinkUI.paint 1) \n\n"+e.getLocalizedMessage()); //$NON-NLS-1$ } Font newFont = new Font(font.getFontName(), font.getStyle(), p1.x); g.setFont(newFont); int maxWidth = maxTextWidth; FontMetrics fm = g.getFontMetrics(); labelRectangle = null; textRowElements = new Vector(); Rectangle viewR = new Rectangle(link.getSize()); Insets viewInsets = link.getInsets(); viewR.x = viewInsets.left; viewR.y = viewInsets.top; viewR.width -= (viewInsets.left + viewInsets.right); viewR.height -= (viewInsets.top + viewInsets.bottom); int textWidth = fm.stringWidth( text ); Rectangle textRow = new Rectangle(); textRow.width = maxWidth; textRow.height = fm.getAscent()+fm.getDescent(); if (textWidth == 0 && viewR.width >= DEFAULT_TEXT_LENGTH) textWidth = DEFAULT_TEXT_LENGTH; if (textWidth < viewR.width && textWidth <= maxWidth) textRow.width = textWidth; //if ((fm.getAscent() + fm.getDescent()) < viewR.height) // textR.height = fm.getAscent() + fm.getDescent(); if (textHeight == -1) textHeight = viewR.height; if (textHeight < viewR.height) { textRow.y = viewR.y + ((viewR.height - textHeight) / 2) + fm.getAscent(); } else { textRow.y = viewR.y; } textRow.x = viewR.x + ((viewR.width - maxWidth) / 2); labelRectangle = new Rectangle(textRow); labelRectangle.x = textRow.x; labelRectangle.y = textRow.y-fm.getAscent(); labelRectangle.width = maxWidth; labelRectangle.height = textHeight; int startPos = 0; int stopPos = 0; g.setColor(Color.black); if (text.length() > maxCharWidth) { int row = -1; String textLeft = text; boolean isRowWithCaret = false; while ( textLeft.length() > 0 ) { row ++; isRowWithCaret = false; startPos = stopPos; int textLen = textLeft.length(); int curLen = maxCharWidth; if (textLen < maxCharWidth ) { curLen = textLen; } String nextText = textLeft.substring(0, curLen); if (curLen < textLen) { int lastSpace = nextText.lastIndexOf(" "); //$NON-NLS-1$ if (lastSpace != -1 && lastSpace != textLen) { curLen = lastSpace+1; nextText = textLeft.substring(0, curLen); textLeft = textLeft.substring(curLen); } else { nextText = textLeft.substring(0, curLen); textLeft = textLeft.substring(curLen); } } else { if (!textLeft.equals("")) //$NON-NLS-1$ nextText = textLeft; textLeft = ""; //$NON-NLS-1$ } stopPos += nextText.length(); int mousePosition = -1; // for dragging mouse to select if (link.hasFocus() && editing && (bDragging || doubleClicked)) { if (editY >= textRow.y-fm.getAscent() && editY < (textRow.y+fm.getDescent()) ) { int tX = textRow.x; int tY = textRow.y; int tWidth = fm.stringWidth( nextText ); if (tWidth < maxWidth) { tX += (maxWidth-tWidth)/2; } int caretPos = 0; if (editX <= tX) caretPos = 0; else if (editX >= tX+tWidth && startPos+nextText.length() == text.length()) { caretPos = nextText.length(); } else if (editX >= tX+tWidth) { caretPos = nextText.length()-1; } else { int ind = 1; int prev = 0; while(ind <= nextText.length()) { String n = nextText.substring(0, ind); int charX = fm.stringWidth(n); if (editX >= (tX+prev) && editX <= (tX+charX) ) { if ( (tX+prev) - editX < editX - (tX+charX)) caretPos = ind-1; else caretPos = ind; break; } prev = charX; ind++; } } if (bDragging) { if (currentCaretPosition == -1) { currentCaretPosition = startPos+caretPos; startSelection = currentCaretPosition; stopSelection = currentCaretPosition; } else { // IF DRAGGING LEFT if (startPos+caretPos < currentCaretPosition) { if (stopSelection == -1) stopSelection = currentCaretPosition; currentCaretPosition = startPos+caretPos; if (startSelection == -1 || startSelection >= currentCaretPosition) startSelection = currentCaretPosition; else stopSelection = currentCaretPosition; } // IF DRAGGING RIGHT else { if (startSelection == -1) startSelection = currentCaretPosition; currentCaretPosition = startPos+caretPos; if (stopSelection == -1 || stopSelection < currentCaretPosition) stopSelection = currentCaretPosition; } } } // DOUBLE CLICK TO SELECT WORD if (doubleClicked) { int index = nextText.indexOf(" ", caretPos); //$NON-NLS-1$ if (index == -1) stopSelection = startPos + nextText.length(); else { stopSelection = startPos + index; } currentCaretPosition = stopSelection; String bit = nextText.substring(0, caretPos); index = bit.lastIndexOf(" "); //$NON-NLS-1$ if (index == -1) startSelection = startPos; else { startSelection = startPos + index+1; } doubleClicked = false; } } } // If there is no more text break out or it cuases a never-ending loop. if (nextText.equals("")) //$NON-NLS-1$ break; drawText(g, fm, link, nextText, textRow, maxWidth, startPos); // If mouse just clicked if (link.hasFocus() && currentCaretPosition == -1 && editing) { // IS THE CLICK IN THIS ROW if (editY >= textRow.y-fm.getAscent() && editY < (textRow.y+fm.getDescent()) ) { int tX = textRow.x; int tY = textRow.y; int tWidth = fm.stringWidth( nextText ); if (tWidth < maxWidth) { tX += (maxWidth-tWidth)/2; } int caretPos = 0; if (editX <= tX) caretPos = 0; else if (editX >= tX+tWidth && startPos+nextText.length() == text.length()) { caretPos = nextText.length(); } else if (editX >= tX+tWidth) { caretPos = nextText.length()-1; } else { int ind = 1; int prev = 0; while(ind <= nextText.length()) { String n = nextText.substring(0, ind); int charX = fm.stringWidth(n); if (editX >= (tX+prev) && editX <= (tX+charX) ) { if ( (tX+prev) - editX < editX - (tX+charX)) caretPos = ind-1; else caretPos = ind; break; } prev = charX; ind++; } } currentCaretPosition = startPos+caretPos; currentRow = row; isRowWithCaret = true; setCaretRectangle(g, fm, textRow, nextText, caretPos, maxWidth); } } else if (link.hasFocus() && editing) { if (caretUp) { // IF WE ARE ALREADY ON THE FIRST ROW if (currentRow == 0) { currentCaretPosition = 0; caretUp = false; } } if (caretDown) { // IF WE ARE ALREADY ON THE LAST ROW if (stopPos == text.length() && currentRow == row) { currentCaretPosition = text.length(); caretDown = false; } } if (currentCaretPosition >= startPos && (currentCaretPosition < stopPos || currentCaretPosition == stopPos && stopPos == text.length())) { int caretPos = currentCaretPosition - startPos; setCaretRectangle(g, fm, textRow, nextText, caretPos, maxWidth); currentRow = row; } isRowWithCaret = true; } TextRowElement element = new TextRowElement(nextText, startPos, new Rectangle(textRow.x, textRow.y, textRow.width, textRow.height), isRowWithCaret); textRowElements.addElement(element); textRow.y += fm.getAscent() + fm.getDescent(); } if (caretUp) { if (currentRow > 0) { caretUp = false; recalculateCaretRectangle(g, fm, maxWidth, true); } } else if (caretDown) { if (currentRow < textRowElements.size()-1) { recalculateCaretRectangle(g, fm, maxWidth, false); caretDown = false; } } } else { // if dragging mouse to select text or double clicked to select word calculate selection if (link.hasFocus() && editing && (bDragging || doubleClicked)) { int tX = textRow.x; int tY = textRow.y; int tWidth = fm.stringWidth( text ); if (tWidth < maxWidth) { tX += (maxWidth-tWidth)/2; } int caretPos = 0; if (editX <= tX) caretPos = 0; else if (editX >= tX+tWidth) caretPos = text.length(); else { int ind = 1; int prev = 0; while(ind <= text.length()) { String n = text.substring(0, ind); int charX = fm.stringWidth(n); if (editX >= (tX+prev) && editX <= (tX+charX) ) { if ( (tX+prev) - editX < editX - (tX+charX)) caretPos = ind-1; else caretPos = ind; break; } prev = charX; ind++; } } if (bDragging) { if (currentCaretPosition == -1) { currentCaretPosition = caretPos; startSelection = currentCaretPosition; stopSelection = currentCaretPosition; } else { // IF DRAGGING LEFT if (caretPos < currentCaretPosition) { if (stopSelection == -1) stopSelection = currentCaretPosition; currentCaretPosition = caretPos; if (startSelection == -1 || startSelection >= currentCaretPosition) startSelection = currentCaretPosition; else stopSelection = currentCaretPosition; } // IF DRAGGING RIGHT else { if (startSelection == -1) startSelection = currentCaretPosition; currentCaretPosition = caretPos; if (stopSelection == -1 || stopSelection < currentCaretPosition) stopSelection = currentCaretPosition; } } } // DOUBLE CLICK TO SELECT WORD if (doubleClicked) { int index = text.indexOf(" ", caretPos); //$NON-NLS-1$ if (index == -1) stopSelection = text.length(); else { stopSelection = index; } currentCaretPosition = stopSelection; String bit = text.substring(0, caretPos); index = bit.lastIndexOf(" "); //$NON-NLS-1$ if (index == -1) startSelection = 0; else { startSelection = index+1; } doubleClicked = false; } } drawText(g, fm, link, text, textRow, maxWidth, 0); // If mouse just clicked if (link.hasFocus() && editing && currentCaretPosition == -1) { int tX = textRow.x; int tY = textRow.y; int tWidth = fm.stringWidth( text ); if (tWidth < maxWidth) { tX += (maxWidth-tWidth)/2; } int caretPos = 0; if (editX <= tX) caretPos = 0; else if (editX >= tX+tWidth) caretPos = text.length(); else { int ind = 1; int prev = 0; while(ind <= text.length()) { String n = text.substring(0, ind); int charX = fm.stringWidth(n); if (editX >= (tX+prev) && editX <= (tX+charX) ) { if ( (tX+prev) - editX < editX - (tX+charX)) caretPos = ind-1; else caretPos = ind; break; } prev = charX; ind++; } } currentCaretPosition = caretPos; currentRow = 0; setCaretRectangle(g, fm, textRow, text, currentCaretPosition, maxWidth); } else if (link.hasFocus() && editing) { // IF UP/DOWN KEY PRESSED ON A SINGLE LINE LABEL GO HOME/END if (caretUp) { currentCaretPosition = 0; caretUp = false; } if (caretDown) { currentCaretPosition = text.length(); caretDown = false; } currentRow = 0; setCaretRectangle(g, fm, textRow, text, currentCaretPosition, maxWidth); } } if (editing) { // PAINT CARET if (caretRectangle != null) { Color oldCol = g.getColor(); g.setColor(Color.red); g.fillRect(caretRectangle.x, caretRectangle.y, caretRectangle.width, caretRectangle.height); g.setColor(oldCol); } // PAINT SUROUNDING BOX g.setColor(Color.blue); g.drawRect(labelRectangle.x-1, labelRectangle.y-1, labelRectangle.width+1, labelRectangle.height+1); } else if (link.isRollover()) { g.setColor(ROLLOVER_COLOR); g.drawRect(labelRectangle.x-1, labelRectangle.y-1, labelRectangle.width+1, labelRectangle.height+1); } } /** * A helper method for the <code>paint</code> method. * Calulate the caretRectangle position when it is moving up or down a row. * * @param g, the current Graphics context. * @param fm, the font metrics to use. * @param maxWidth, the maximum width for the node. * @param isUp, is the caret moving up a row? True equals up a row, false equals down a row. */ private void recalculateCaretRectangle(Graphics g, FontMetrics fm, int maxWidth, boolean isUp) { if (isUp) currentRow--; else currentRow++; TextRowElement element = (TextRowElement)textRowElements.elementAt(currentRow); Rectangle currR = element.getTextRect(); String currText = element.getText(); int currStart = element.getStartPosition(); Rectangle oldCaretRectangle = new Rectangle(caretRectangle.x, caretRectangle.y, caretRectangle.width, caretRectangle.height); int textWidth = fm.stringWidth( currText ); if (textWidth < maxWidth) { currR.x += (maxWidth-textWidth)/2; } if (oldCaretRectangle.x <= currR.x) { caretRectangle.x = currR.x; currentCaretPosition = currStart; } if (oldCaretRectangle.x >= currR.x+textWidth) { caretRectangle.x = currR.x+textWidth; currentCaretPosition = currStart+currText.length(); } else { // FIND THE CLOSEST CHAR TO THE ONE ABOVE WHERE THE CARET WAS int caretPos = 0; int ind = 1; int prev = 0; int length = currText.length(); while(ind <= length) { String n = currText.substring(0, ind); int charX = fm.stringWidth(n); if ( (currR.x+charX) > oldCaretRectangle.x ) { if ( oldCaretRectangle.x - (currR.x+prev) < (currR.x+charX)- oldCaretRectangle.x ) caretPos = ind-1; else caretPos = ind; break; } prev = charX; ind++; } currentCaretPosition = currStart+caretPos; caretRectangle.x = currR.x+fm.stringWidth(currText.substring(0, caretPos)) - 1; } caretRectangle.y = currR.y - fm.getAscent()+1; caretRectangle.width = oldCaretRectangle.width; caretRectangle.height = oldCaretRectangle.height; } /** * A helper method for the <code>paint</code> method. Calulate and set the caretRectangle. * * @param g, the current Graphics context. * @param fm, the font metrics to use. * @param textR, the rectangle of the current text area of this label row. * @param text, the row of text being addressed. * @param caretIndex, the current position of the caret in this row. * @param maxWidth, the maximum width for the node. * @param isUp, is the caret moving up a row? True equals up a row, false equals down a row. */ private void setCaretRectangle(Graphics g, FontMetrics fm, Rectangle textR, String text, int caretIndex, int maxWidth) { if (caretIndex >= 0 && caretIndex <= text.length() ) { caretRectangle = new Rectangle(); int textX = textR.x; int textY = textR.y; int textWidth = fm.stringWidth( text ); int offset = 0; if (textWidth < maxWidth) { offset = (maxWidth-textWidth)/2; textX += offset; } caretRectangle.x = textX + fm.stringWidth(text.substring(0, caretIndex)); caretRectangle.y = textY - fm.getAscent()+1; caretRectangle.width = 1; caretRectangle.height = fm.getAscent()+1; } } /** * Paint clippedText at textX, textY. * * @param g, the Graphics object to use to do the paint. * @param s, the String of text to paint. * @param textX, the x position to start the paint. * @param textY, the y position to start the paint * @see #paint */ protected void paintEnabledText(Graphics g, String s, int textX, int textY) { BasicGraphicsUtils.drawString(g, s, '\0', textX, textY); } /** * A helper method for the <code>paint</code> method. Paint a row of text. * * @param g the current Graphics context. * @param fm the font metrics to use. * @param link the current link being painted. * @param text the row of text being painted. * @param textR the rectangle of the current text area of this label row. * @param caretIndex the current position of the caret in this row. * @param maxWidth the maximum width for the link. * @param startPos the starting position of this row in the context of the while label. * @param isUp is the caret moving up a row? True equals up a row, false equals down a row. */ private void drawText(Graphics g, FontMetrics fm, UILink link, String text, Rectangle textR, int maxWidth, int startPos) { Color oldColor = null; if (text != null) { int textX = textR.x; int textY = textR.y; int textWidth = fm.stringWidth( text ); if (textWidth < maxWidth) { textR.width = textWidth; textX += (maxWidth-textWidth)/2; } else { textR.width = maxWidth; } // text background will always be opaque oldColor = g.getColor(); int stopPos = startPos+text.length(); if (link.isSelected()) { if (editing && (startSelection > -1 && stopSelection > -1 && stopSelection > startSelection) && (startSelection < stopPos) && (startSelection >= startPos || stopSelection > startPos) ) { // ONLY PAINT THE PART INSIDE SELECTION int begin = startSelection-startPos; int end = stopSelection-startPos; if (end > text.length()) end = text.length(); if (begin < 0) begin = 0; String beginText = text.substring(0, begin); String selText = text.substring(begin, end); String endText = text.substring(end); int beginWidth = fm.stringWidth(beginText); int selWidth = fm.stringWidth(selText); g.setColor(EDITING_COLOR); g.fillRect(textX, textY-fm.getAscent(), beginWidth, textR.height); g.setColor(TSELECTED_COLOR); g.fillRect(textX+beginWidth, textY-fm.getAscent(), selWidth, textR.height); g.setColor(EDITING_COLOR); g.fillRect(textX+beginWidth+selWidth, textY-fm.getAscent(), textR.width-selWidth, textR.height); g.setColor(SELECTED_TEXT_COLOR); paintEnabledText(g, beginText, textX, textY); g.setColor(TSELECTED_TEXT_COLOR); paintEnabledText(g, selText, textX+beginWidth, textY); g.setColor(SELECTED_TEXT_COLOR); paintEnabledText(g, endText, textX+beginWidth+selWidth, textY); g.setColor(oldColor); } else { if (editing) g.setColor(EDITING_COLOR); else g.setColor(SELECTED_COLOR); g.fillRect(textX, textY-fm.getAscent(), textR.width, textR.height); g.setColor(SELECTED_TEXT_COLOR); paintEnabledText(g, text, textX, textY); g.setColor(oldColor); } } else { Color background = Model.BACKGROUND_DEFAULT; if (link.getLinkProperties() != null) { background = new Color(link.getLinkProperties().getBackground()); } g.setColor(background); g.fillRect(textX, textY-fm.getAscent(), textR.width, textR.height); g.setColor(oldColor); Color foreground = Model.FOREGROUND_DEFAULT; if (link.getLinkProperties() != null) { foreground = new Color(link.getLinkProperties().getForeground()); } g.setColor(foreground); paintEnabledText(g, text, textX, textY); g.setColor(oldColor); } } } ////////////////////////////////////////////////////////////////////////////////////////////////////////////// /** * Add the passed string to the link label at the point of the current caret position. * * @param key, the string to add to the label. */ private void addCharToLabel(String key) { String oldText = oLink.getText(); if (startSelection > -1 && stopSelection > -1 && stopSelection > startSelection) { String text = oldText.substring(0, startSelection) + oldText.substring(stopSelection); currentCaretPosition = startSelection; startSelection = -1; stopSelection = -1; oldText = text; } if(oldText.equals(ICoreConstants.NOLABEL_STRING)) { oldText = ""; //$NON-NLS-1$ currentCaretPosition = 0; } String newText = ""; //$NON-NLS-1$ if (currentCaretPosition < oldText.length()) newText = oldText.substring(0, currentCaretPosition) + key + oldText.substring(currentCaretPosition); else newText = oldText + key; currentCaretPosition ++; previousString = oldText; oLink.setText(newText); ProjectCompendium.APP.setStatus(newText); } /** * If editing, do a paste from the clipboard into the link label. */ public void paste() { if (editing) { String oldText = oLink.getText(); previousString = oLink.getText(); if (startSelection > -1 && stopSelection > -1 && stopSelection > startSelection) { String text = oldText.substring(0, startSelection) + oldText.substring(stopSelection); currentCaretPosition = startSelection; startSelection = -1; stopSelection = -1; oldText = text; } if (clipboard == null) clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); Transferable clipData = clipboard.getContents(this); String s=""; //$NON-NLS-1$ try { s = (String)(clipData.getTransferData(DataFlavor.stringFlavor)); } catch (Exception ufe) {} if (oldText.equals(ICoreConstants.NOLABEL_STRING)) { currentCaretPosition = 0; oldText = ""; //$NON-NLS-1$ } String text = oldText.substring(0, currentCaretPosition) + s + oldText.substring(currentCaretPosition); currentCaretPosition+=s.length(); oLink.setText(text); ProjectCompendium.APP.setStatus(text); } } /** * If editing, do a cut to the clipboard from the link label. */ public void cut() { if (editing) { String text = oLink.getText(); if (startSelection < 0 || stopSelection < 0 || stopSelection > text.length()) return; if (clipboard == null) clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); StringSelection data; String selectedText = text.substring(startSelection, stopSelection); data = new StringSelection(selectedText); clipboard.setContents(data, data); previousString = text; text = text.substring(0, startSelection) + text.substring(stopSelection); oLink.setText(text); currentCaretPosition = startSelection; startSelection = -1; stopSelection = -1; oLink.repaint(); } } /** * If editing, delete selected text from the link label. */ public void delete() { if (editing) { if (startSelection > -1 && stopSelection > -1 && stopSelection > startSelection) { String text = oLink.getText(); previousString = text; text = text.substring(0, startSelection) + text.substring(stopSelection); currentCaretPosition = startSelection; startSelection = -1; stopSelection = -1; oLink.setText(text); oLink.repaint(); ProjectCompendium.APP.setStatus(text); } else { String text = oLink.getText(); previousString = text; if (text.equals(ICoreConstants.NOLABEL_STRING)) { text = " "; //$NON-NLS-1$ currentCaretPosition = 0; } else if (currentCaretPosition >= 0 && currentCaretPosition < text.length() && text != null && !text.equals("")) { //$NON-NLS-1$ text = text.substring(0, currentCaretPosition) + text.substring(currentCaretPosition+1); } oLink.setText(text); oLink.repaint(); ProjectCompendium.APP.setStatus(text); } } } /** * If editing, do a copy to the clipboard from the link label. */ public void copy() { if (editing) { String text = oLink.getText(); if (startSelection < 0 || stopSelection < 0 || stopSelection > text.length()) return; if (clipboard == null) clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); StringSelection data; String selectedText = text.substring(startSelection, stopSelection); data = new StringSelection(selectedText); clipboard.setContents(data, data); } } /** * If editing, select all the text in the link label. */ public void selectAll() { if (editing) { String temp = oLink.getText(); startSelection = 0; stopSelection =temp.length(); oLink.repaint(); } } /** * Invoked when a key is typed in a component. * @param e, the associated KeyEvent. */ // NEED THIS HEAR TO PICK UP KEYCHARS FROM MULTIPLE KEYS LIKE ACCENTED LETTER ON FOREIGHN KEYBOARDS. public void keyTyped(KeyEvent evt) { if (oLink != null && !oLink.hasFocus()) { return; } // IF EDITING THE TEXT AREA if (editing) { char keyChar = evt.getKeyChar(); char[] key = {keyChar}; sKeyPressed = new String(key); int modifiers = evt.getModifiers(); if (ProjectCompendium.isMac && modifiers == shortcutKey) { evt.consume(); } else { if (( Character.isLetterOrDigit(keyChar) || sKeyPressed.equals(" ") || //$NON-NLS-1$ IUIConstants.NAVKEYCHARS.indexOf(sKeyPressed) != -1) ) { addCharToLabel(sKeyPressed); evt.consume(); } } } } /** * Invoked when a key is pressed in a component. * @param evt, the associated KeyEvent. */ public void keyPressed(KeyEvent evt) { char keyChar = evt.getKeyChar(); char[] key = {keyChar}; sKeyPressed = new String(key); int keyCode = evt.getKeyCode(); int modifiers = evt.getModifiers(); // IF EDITING THE TEXT AREA if (editing) { if (modifiers == java.awt.Event.CTRL_MASK && keyCode == KeyEvent.VK_LEFT) { if (currentCaretPosition > 0) { String text = oLink.getText(); String section = text.substring(0, currentCaretPosition); int index = section.lastIndexOf(" "); //$NON-NLS-1$ if (index == section.length()-1) { section = section.substring(0, section.length()-2); index = section.lastIndexOf(" ") - 1; //$NON-NLS-1$ if (index == -1) { currentCaretPosition = 0; } else { currentCaretPosition = currentCaretPosition - ( section.length() - index ); } } else if (index == -1) { currentCaretPosition = 0; } else { currentCaretPosition = currentCaretPosition - ( section.length() - index - 1 ); } } oLink.repaint(); evt.consume(); } else if (modifiers == java.awt.Event.CTRL_MASK && keyCode == KeyEvent.VK_RIGHT) { String text = oLink.getText(); if (currentCaretPosition < text.length()) { String section = text.substring(currentCaretPosition); int index = section.indexOf(" "); //$NON-NLS-1$ if (index == currentCaretPosition+1) { section = section.substring(1); index = section.indexOf(" ")+1; //$NON-NLS-1$ } if (index == -1) currentCaretPosition = text.length(); else currentCaretPosition += index+1; } oLink.repaint(); evt.consume(); } else if (modifiers == shortcutKey) { switch(keyCode) { case KeyEvent.VK_X: { // CUT cut(); evt.consume(); break; } case KeyEvent.VK_C: { // COPY copy(); evt.consume(); break; } case KeyEvent.VK_V: { // PASTE INTO LABEL paste(); evt.consume(); break; } case KeyEvent.VK_Z: { // UNDO LABEL PASTE String temp = oLink.getText(); oLink.setText(previousString); currentCaretPosition = previousString.length(); previousString = temp; break; } case KeyEvent.VK_A: { // SELECT ALL selectAll(); evt.consume(); break; } } } // FOLLOW SECTIONS FOR MIMICING TEXT FIELD BEHAVIOUR else if (keyCode == KeyEvent.VK_HOME && modifiers == 0) { currentCaretPosition=0; oLink.repaint(); evt.consume(); } else if (keyCode == KeyEvent.VK_END && modifiers == 0) { String text = oLink.getText(); currentCaretPosition=text.length(); oLink.repaint(); evt.consume(); } else if (keyCode == KeyEvent.VK_UP && modifiers == 0) { startSelection = -1; stopSelection = -1; caretUp = true; oLink.repaint(); evt.consume(); } else if (keyCode == KeyEvent.VK_DOWN && modifiers == 0) { startSelection = -1; stopSelection = -1; caretDown = true; oLink.repaint(); evt.consume(); } else if (keyCode == KeyEvent.VK_LEFT && modifiers == 0) { startSelection = -1; stopSelection = -1; if (currentCaretPosition > 0) currentCaretPosition--; oLink.repaint(); evt.consume(); } else if (keyCode == KeyEvent.VK_RIGHT && modifiers == 0) { startSelection = -1; stopSelection = -1; String text = oLink.getText(); if (currentCaretPosition < text.length()) currentCaretPosition++; oLink.repaint(); evt.consume(); } // SELECTION else if (keyCode == KeyEvent.VK_HOME && modifiers == Event.SHIFT_MASK) { if (startSelection == -1) startSelection = 0; if (stopSelection == -1) stopSelection = currentCaretPosition; currentCaretPosition=0; oLink.repaint(); evt.consume(); } else if (keyCode == KeyEvent.VK_END && modifiers == Event.SHIFT_MASK) { if (startSelection == -1) startSelection = currentCaretPosition; String text = oLink.getText(); currentCaretPosition=text.length(); stopSelection = currentCaretPosition; oLink.repaint(); evt.consume(); } else if (keyCode == KeyEvent.VK_LEFT && modifiers == Event.SHIFT_MASK) { if (stopSelection == -1) stopSelection = currentCaretPosition; if (currentCaretPosition > 0) currentCaretPosition--; if (startSelection == -1 || (startSelection < stopSelection && startSelection >= currentCaretPosition)) { startSelection = currentCaretPosition; } else stopSelection = currentCaretPosition; oLink.repaint(); evt.consume(); } else if (keyCode == KeyEvent.VK_RIGHT && modifiers == Event.SHIFT_MASK) { if (startSelection == -1) startSelection = currentCaretPosition; String text = oLink.getText(); if (currentCaretPosition < text.length()) currentCaretPosition++; if (stopSelection == -1 || stopSelection < currentCaretPosition) stopSelection = currentCaretPosition; oLink.repaint(); evt.consume(); } else if (keyCode == KeyEvent.VK_LEFT && modifiers == Event.SHIFT_MASK+Event.CTRL_MASK) { if (currentCaretPosition > 0) { if (stopSelection == -1) stopSelection = currentCaretPosition; String text = oLink.getText(); String section = text.substring(0, currentCaretPosition); int index = section.lastIndexOf(" "); //$NON-NLS-1$ if (index == section.length()-1) { section = section.substring(0, section.length()-2); index = section.lastIndexOf(" ") - 1; //$NON-NLS-1$ if (index == -1) currentCaretPosition = 0; else currentCaretPosition = currentCaretPosition - ( section.length() - index ); } else if (index == -1) currentCaretPosition = 0; else currentCaretPosition = currentCaretPosition - ( section.length() - index - 1 ); if (startSelection == -1 || startSelection > currentCaretPosition) startSelection = currentCaretPosition; } oLink.repaint(); evt.consume(); } else if (keyCode == KeyEvent.VK_RIGHT && modifiers == Event.SHIFT_MASK+Event.CTRL_MASK) { String text = oLink.getText(); if (currentCaretPosition < text.length()) { if (startSelection == -1) startSelection = currentCaretPosition; String section = text.substring(currentCaretPosition); int index = section.indexOf(" "); //$NON-NLS-1$ if (index == currentCaretPosition+1) { section = section.substring(1); index = section.indexOf(" ")+1; //$NON-NLS-1$ } if (index == -1) currentCaretPosition = text.length(); else currentCaretPosition += index+1; stopSelection = currentCaretPosition; } oLink.repaint(); evt.consume(); } // DELETING CHARS else if (keyCode == KeyEvent.VK_BACK_SPACE && ( modifiers == 0 || modifiers == Event.SHIFT_MASK)) { if (startSelection > -1 && stopSelection > -1 && stopSelection > startSelection) { String text = oLink.getText(); previousString = text; text = text.substring(0, startSelection) + text.substring(stopSelection); currentCaretPosition = startSelection; startSelection = -1; stopSelection = -1; oLink.setText(text); oLink.repaint(); ProjectCompendium.APP.setStatus(text); } else { String text = oLink.getText(); previousString = text; if (text.equals(ICoreConstants.NOLABEL_STRING)) { text = " "; //$NON-NLS-1$ currentCaretPosition = 0; } else if (currentCaretPosition > 0 && text != null && !text.equals("")) { //$NON-NLS-1$ text = text.substring(0, currentCaretPosition-1) + text.substring(currentCaretPosition); currentCaretPosition--; } if (stopSelection > -1) stopSelection = currentCaretPosition; oLink.setText(text); oLink.repaint(); ProjectCompendium.APP.setStatus(text); } evt.consume(); } else if (keyCode == KeyEvent.VK_DELETE && modifiers == 0) { delete(); evt.consume(); } // MOVED TO KEYTYPED TO PICK UP ACCENTED CHARACTER ETC WHICH ARE DOUBLE KEYSTOKES /*else if ( Character.isLetterOrDigit(keyChar) || sKeyPressed.equals(" ") || IUIConstants.NAVKEYCHARS.indexOf(sKeyPressed) != -1) { addCharToLabel(sKeyPressed); oLink.repaint(); evt.consume(); }*/ } } /** * Handles the single and double click events. * @param evt, the associated MouseEvent. */ public void mouseClicked(MouseEvent evt) { int clickCount = evt.getClickCount(); if (SwingUtilities.isLeftMouseButton(evt)) { int nX = evt.getX(); int nY = evt.getY(); // CHECK LABEL HAS BEEN CLICKED if (labelRectangle != null && labelRectangle.contains(nX, nY)) { if (clickCount == 1) { editing = true; editX = nX; editY = nY; startSelection = -1; stopSelection = -1; currentCaretPosition = -1; doubleClicked = false; oLink.moveToFront(); if (!oLink.isSelected()) { oLink.getViewPane().setSelectedLink(null, ICoreConstants.DESELECTALL); oLink.getViewPane().setSelectedLink(oLink, ICoreConstants.SINGLESELECT); oLink.setSelected(true); } oLink.requestFocus(); evt.consume(); } else if (clickCount == 2) { editing = true; editX = nX; editY = nY; startSelection = -1; stopSelection = -1; currentCaretPosition = -1; doubleClicked=true; oLink.moveToFront(); if (!oLink.isSelected()) { oLink.getViewPane().setSelectedLink(null, ICoreConstants.DESELECTALL); oLink.getViewPane().setSelectedLink(oLink, ICoreConstants.SINGLESELECT); oLink.setSelected(true); } oLink.requestFocus(); evt.consume(); } } else { editing = false; doubleClicked = false; oLine.getParent().dispatchEvent(evt); } } else { oLine.getParent().dispatchEvent(evt); } } /** * Checks whether the given point (pt) is on or close to the * line of this link when it is a curved line or a squared line. * * @param pt the point to check. * @param d the tolerance for the check. */ public boolean isOnLine(Point pt) { Container parent = oLink.getParent(); Point newPoint = (Point)pt.clone(); if (parent != null) newPoint = SwingUtilities.convertPoint(parent, pt, oLink); boolean isOnLink = false; if (isCurved) { if (oLinkPath != null) { // allow for thickness. double tol = (nThickness/2)+LINE_TOLERANCE; double tols = tol/2; isOnLink = oLinkPath.intersects(new Integer(newPoint.x).doubleValue()-tols, new Integer(newPoint.y).doubleValue()-tols, tol, tol ); } } else if (isSquared) { //Tolerance and half thickness built in to these rectangles when created. if (oRectOne != null && oRectTwo != null && oRectThree != null && (oRectOne.contains(newPoint) || oRectTwo.contains(newPoint) || oRectThree.contains(newPoint))) { isOnLink = true; } } else { isOnLink = oLink.onLine(pt, LINE_TOLERANCE); } return isOnLink; } /** * Handles the initiation of drag and drop events. * @param evt, the associated MouseEvent. */ public void mousePressed(MouseEvent evt) { startSelection = -1; stopSelection = -1; doubleClicked = false; bDragging = true; if (editing && SwingUtilities.isLeftMouseButton(evt)) { if (labelRectangle != null && labelRectangle.contains(evt.getX(), evt.getY())) { editX = evt.getX(); editY = evt.getY(); currentCaretPosition = -1; oLink.repaint(); return; } else { editing = false; } } else { oLine.getParent().dispatchEvent(evt); } } /** * Handles drag and drop finish operations. * @param evt, the associated MouseEvent. */ public void mouseReleased(MouseEvent evt) { Point p = SwingUtilities.convertPoint((Component)evt.getSource(), evt.getX(), evt.getY(), null); boolean isLeftMouse = SwingUtilities.isLeftMouseButton(evt); boolean isRightMouse = SwingUtilities.isRightMouseButton(evt); if (bDragging) { if (isLeftMouse && !evt.isAltDown() && editing) { if (labelRectangle != null && labelRectangle.contains(evt.getX(), evt.getY())) { editX = evt.getX(); editY = evt.getY(); oLink.repaint(); } } else { oLine.getParent().dispatchEvent(evt); } bDragging = false; } else { oLine.getParent().dispatchEvent(evt); } } /** * Invoked when a mouse is dragged (pressed and moved). * @param evt, the associated MouseEvent. */ public void mouseDragged(MouseEvent evt) { if (bDragging) { if (SwingUtilities.isLeftMouseButton(evt) && !evt.isAltDown() && editing) { if (labelRectangle != null && labelRectangle.contains(evt.getX(), evt.getY())) { editX = evt.getX(); editY = evt.getY(); oLink.repaint(); return; } } else { oLine.getParent().dispatchEvent(evt); } } else { oLine.getParent().dispatchEvent(evt); } } /** * Calculate the size of the max with and height for the label text area. * @param rFrom, the from node. * @param rTo, the to node. * @param viewR, the link palette size. * @param fm, the fontmetrics to use. */ private Dimension calculateMaxSize(Rectangle rFrom, Rectangle rTo, FontMetrics fm) { int maxWidth = -1; int maxHeight = -1; int charWidth = fm.charWidth('W'); int lineHeight = fm.getAscent() + fm.getDescent(); //(fm.getDescent()*2) // IF THE FROM NODE IS TOP if (rFrom.y < rTo.y) { // IF DRAWING HORIZONAL BOXES - THERE MUST BE ROOM FOR AT LEAST 1 LINE OF TEXT if (rFrom.y + rFrom.height+lineHeight < rTo.y) { maxHeight = rTo.y - (rFrom.y + rFrom.height); maxWidth = 0; // UNLIMITED } // IF VERTICAL VERTICAL BOXES - THERE MUST BE ROOM FOR AT LEAST 1 CHARACTER else if (rFrom.x + rFrom.width+charWidth < rTo.x) { maxWidth = rTo.x - (rFrom.x + rFrom.width); maxHeight = 0; // UNLIMITED } else if (rTo.x + rTo.width+fm.charWidth('W') < rFrom.x) { maxWidth = rFrom.x - (rTo.x + rTo.width); maxHeight = 0; // UNLIMITED } else { //no room for text. } } // IF THE TO NODE IS TOP else { // IF DRAWING HORIZONAL BOXES if (rTo.y + rTo.height + lineHeight < rFrom.y) { maxHeight = rFrom.y - (rTo.y + rTo.height); maxWidth = 0; // UNLIMITED } // IF VERTICAL VERTICAL BOXES - THERE MUST BE ROOM FOR AT LEAST 1 CHARACTER else if (rFrom.x + rFrom.width+charWidth < rTo.x) { maxWidth = rTo.x - (rFrom.x + rFrom.width); maxHeight = 0; // UNLIMITED } else if (rTo.x + rTo.width+charWidth < rFrom.x) { maxWidth = rFrom.x - (rTo.x + rTo.width); maxHeight = 0; // UNLIMITED } else { // no room for text } } return new Dimension(maxWidth, maxHeight); } /** * Calculate the required dimensions of the given link. * @param link the link to calculate the dimensions for. * @return Dimension the dimension for the given link. */ private Dimension calculateDimension(UILink link) { String text = link.getText(); Insets insets = link.getInsets(); int dx = insets.left + insets.right; int dy = insets.top + insets.bottom; Point from = link.getFrom(); Point to = link.getTo(); if (from == null || to == null) return new Dimension(0,0); int width = Math.abs(from.x - to.x); int height = Math.abs(from.y - to.y); // make sure that the width and height are large enough // to fit a possible arrow, the line thickness and takes into account the minimum width int w = Math.max(link.getLineThickness(), link.getMinWidth()); if ((link.getArrow() != ICoreConstants.NO_ARROW) && ((link.getCurrentArrowHeadWidth()*4) > w)) w = link.getCurrentArrowHeadWidth()*4; width += w; height += w; linkDimension = new Dimension(width, height); Font font = link.getFont(); FontMetrics fm = link.getFontMetrics(font); Rectangle textR = new Rectangle(); Rectangle viewR = new Rectangle(link.getSize()); viewR.x = insets.left; viewR.y = insets.top; viewR.width = width; viewR.height = height; int textWidth = fm.stringWidth( text ); int widestLine = 0; UINode fromNode = link.getFromNode(); UINode toNode = link.getToNode(); Rectangle rFrom = fromNode.getBounds(); Rectangle rTo = toNode.getBounds(); Dimension maxSize = calculateMaxSize(rFrom, rTo, fm); int maxWidth = maxSize.width; int maxHeight = maxSize.height; int wrapWidth = Model.LABEL_WRAP_WIDTH_DEFAULT; LinkProperties props = link.getLinkProperties(); if (props != null) { wrapWidth = link.getLinkProperties().getLabelWrapWidth(); } if (wrapWidth <= 0) { wrapWidth = ((Model)ProjectCompendium.APP.getModel()).labelWrapWidth; } wrapWidth = wrapWidth+1; // Needs this for some reason. int preferredWrapWidth = fm.charWidth('W') * wrapWidth; // NO ROOM FOR TEXT if (maxWidth == -1 && maxHeight == -1) { Dimension rv = new Dimension(width, height); rv.width += dx+1; rv.height += dy; textWidth = 0; textHeight = 0; return rv; } // UNLIMITED WIDTH AVAILABLE - MAKE THE MAXWIDTH THE WRAPWIDTH else if (maxWidth == 0) { maxCharWidth = wrapWidth; maxWidth = preferredWrapWidth; } // UNLIMITED HEIGHT AVAILABLE else if (maxHeight == 0) { if (maxWidth > preferredWrapWidth) maxCharWidth = wrapWidth; else { maxCharWidth = maxWidth / fm.charWidth('W'); } } else { maxCharWidth = maxWidth / fm.charWidth('W'); } if (textWidth == 0) { textWidth = DEFAULT_TEXT_LENGTH; } textR.width = textWidth; // When there is no text draw a small box to type into textR.height = 0; textR.x = dx; if (text.length() > maxCharWidth) { // first calculate widestLine int loop = -1; String textLeft = text; while ( textLeft.length() > 0 ) { loop ++; int textLen = textLeft.length(); int curLen = maxCharWidth; if (textLen < maxCharWidth ) { curLen = textLen; } String nextText = textLeft.substring(0, curLen); if (curLen < textLen) { int lastSpace = nextText.lastIndexOf(" "); //$NON-NLS-1$ if (lastSpace != -1 && lastSpace != textLen) { curLen = lastSpace+1; nextText = textLeft.substring(0, curLen); textLeft = textLeft.substring(curLen); } else { nextText = textLeft.substring(0, curLen); textLeft = textLeft.substring(curLen); } } else { nextText = textLeft; textLeft = ""; //$NON-NLS-1$ } textR.height += fm.getAscent() + fm.getDescent(); int thisWidth = fm.stringWidth( nextText ); if ( thisWidth > widestLine) { widestLine = thisWidth; } } } else { if (maxWidth > 0) { widestLine = textWidth; textR.height += fm.getAscent() + fm.getDescent(); } else { widestLine = 0; textR.height += fm.getAscent() + fm.getDescent(); } } if (widestLine == 0) { widestLine = DEFAULT_TEXT_LENGTH; } maxTextWidth = widestLine; if (widestLine > width) { width = widestLine; } if (textR.height == 0) { textR.height += fm.getAscent() + fm.getDescent(); } if (textR.height > height) { height = textR.height; textHeight = height; } else { textHeight = textR.height; } //Rectangle main = new Rectangle(width, height); //Dimension rv = main.union(textR).getSize(); Dimension rv = new Dimension(width, height); rv.width += dx+2; rv.height += dy+2; return rv; } /** * Return the component preferred size. * @param c, the component to return the preferred size for. * @return Dimension, the preferred size for the given component. */ public Dimension getPreferredSize(JComponent c) { return calculateDimension((UILink)c); } /** * @return getPreferredSize(c) */ public Dimension getMinimumSize(JComponent c) { return getPreferredSize(c); } /** * @return getPreferredSize(c) */ public Dimension getMaximumSize(JComponent c) { return getPreferredSize(c); } /** * Return the preferred bounds of this component. * @param c, the component to return the preferred bounds for. * @return Rectangle, the preferred bounds of this component. */ public Rectangle getPreferredBounds(JComponent c) { UILink link = (UILink)c; Point from = link.getFrom(); Point to = link.getTo(); if (from == null || to == null) return new Rectangle(0,0,0,0); Dimension size = getPreferredSize(c); // determine absolute to and from points in parent's coordinate system Point f = new Point(0,0); Point t = new Point(0,0); if (oLine.getCoordinateType() == UILine.ABSOLUTE) { // coordinates already relative to this components coordinate system f = from; t = to; } else { // calculate the absolute coordinates by converting the coordinates from // this components coordinate system to the parents coordinate system Container parent = link.getParent(); if (parent != null) { f = SwingUtilities.convertPoint(link, from, parent); t = SwingUtilities.convertPoint(link, to, parent); } } int x = Math.min(f.x, t.x); int y = Math.min(f.y, t.y); // give room for possible arrow and/or line thickness int w = Math.max(link.getLineThickness(), link.getMinWidth()); if ((link.getArrow() != ICoreConstants.NO_ARROW) && (link.getCurrentArrowHeadWidth()*4 > w)) w = link.getCurrentArrowHeadWidth()*4; x -= w/2; y -= w/2; // ADJUST THE X AND Y LOCATION TO CENTER POSSIBLE LARGER BOX // DO WE NEED TO MOVE THE X POS if (size.width > linkDimension.width) { x -= (size.width-linkDimension.width)/2; } // DO WE NEED TO MOVE THE Y POS if (size.height > linkDimension.height) { y -= (size.height-linkDimension.height)/2; } return new Rectangle(x, y, size.width, size.height); } private Point reduceLine(Point from, Point to, int lineWidth) { double width = 0; if (to.x>from.x) { width = to.x-from.x; } else { width = from.x-to.x; } double height = 0; if (to.y>from.y) { height = to.y-from.y; } else { height = from.y-to.y; } //double lineLength = Math.sqrt((width*width)+(height*height)); int positionAddition = lineWidth*2; /*if (lineLength <= 20) { positionAddition = 2; }*/ double angleRads = Math.atan(height/width); double newHeight = Math.sin(angleRads)*positionAddition; double newWidth = Math.cos(angleRads)*positionAddition; Point p1 = new Point(); if (from.x < to.getX()) { if (from.getY() > to.getY()) { p1 = new Point(new Double(to.x-newWidth).intValue(), new Double(to.y+newHeight).intValue()); //Top-right quarter } else { p1 = new Point(new Double(to.x-newWidth).intValue(), new Double(to.y-newHeight).intValue()); // Bottom-right quarter } } else { if (from.getY() > to.getY()) { p1 = new Point(new Double(to.x+newWidth).intValue(), new Double(to.y+newHeight).intValue()); //Top-Left quarter } else { p1 = new Point(new Double(to.x+newWidth).intValue(), new Double(to.y-newHeight).intValue()); // Bottom-Left quarter } } return p1; } /** * Refresh the bounds of this object. * Calls <code>getPreferredSize</code>, * which is important for the paint method to work correctly later.# * @see #getPreferredSize */ public void refreshBounds() { oLink.setBounds(getPreferredBounds(oLink)); } /** * Return the rectangle for this link's label area. * @return Rectangle, the rectagnle for this link's label area. */ public Rectangle getLabelRectangle() { return labelRectangle; } }