/* * Copyright 2001-2008 Geert Bevin (gbevin[remove] at uwyn dot com) * Licensed under the Apache License, Version 2.0 (the "License") * $Id: Element.java 3918 2008-04-14 17:35:35Z gbevin $ */ package com.uwyn.rife.gui.old; import java.awt.*; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.awt.font.LineMetrics; import java.awt.geom.*; import java.awt.image.BufferedImage; import java.util.ArrayList; import javax.swing.JComponent; import javax.swing.JMenu; import javax.swing.JMenuItem; import javax.swing.JPopupMenu; import com.uwyn.rife.config.Config; import com.uwyn.rife.gui.Rife; import com.uwyn.rife.swing.JDialogSystemError; import com.uwyn.rife.tools.ExceptionUtils; import com.uwyn.rife.tools.Localization; public class Element extends JComponent implements MouseListener, MouseMotionListener { private boolean mSelected = false; private Color mTitleBackgroundColor = new Color(220, 220, 220); private StructurePanel mStructurePanel = null; private ElementStyle mElementStyleOrig = null; private ElementStyle mElementStyleScaled = null; private ArrayList<ElementListener> mElementListeners = null; private ArrayList<ElementProperty> mElementProperties = null; private float mXOrig = -1f; private float mYOrig = -1f; private float mWidthOrig = -1f; private float mHeightOrig = -1f; private float mBodyXOffsetOrig = -1f; private float mBodyYOffsetOrig = -1f; private Area mBoundingOrig = null; private Area mBodyAreaOutsideOrig = null; private Area mBodyAreaInsideOrig = null; private GeneralPath mUsedParameterLinesOrig = null; private Area mTitleRectangleOutsideOrig = null; private Area mTitleRectangleInsideOrig = null; private float mScaleFactor = 1f; private float mXScaled = -1f; private float mYScaled = -1f; private float mWidthScaled = -1f; private float mHeightScaled = -1f; private Area mBoundingScaled = null; private Area mBodyAreaOutsideScaled = null; private Area mBodyAreaInsideScaled = null; private GeneralPath mUsedParameterLinesScaled = null; private Area mTitleRectangleOutsideScaled = null; private Area mTitleRectangleInsideScaled = null; private boolean mDragActive = false; private Point mDragStartPoint = null; private BufferedImage mDragBufferedImage = null; private boolean mIsTransparent = false; public Element(StructurePanel structurePanel, ElementStyle styleOrig, ElementStyle styleScaled) { this(structurePanel, "", styleOrig, styleScaled); } public Element(StructurePanel structurePanel, String title, ElementStyle styleOrig, ElementStyle styleScaled) { super(); mStructurePanel = structurePanel; mElementStyleOrig = styleOrig; mElementStyleScaled = styleScaled; mElementListeners = new ArrayList<ElementListener>(); mElementProperties = new ArrayList<ElementProperty>(); addProperty(ElementPropertyTitle.class, title); setOpaque(false); setLayout(null); } public StructurePanel getStructurePanel() { return mStructurePanel; } public int getWidth() { if(mWidthScaled == -1) { createPrecalculatedAreas(); } return (int)mWidthScaled; } public int getHeight() { if(mHeightScaled == -1) { createPrecalculatedAreas(); } return (int)mHeightScaled; } public Area getBoundingAreaOrig() { return mBoundingOrig; } public Area getBoundingAreaScaled() { return mBoundingScaled; } public Area getTitleRectangleOutside() { return mTitleRectangleOutsideScaled; } public Area getTitleRectangleInside() { return mTitleRectangleInsideScaled; } public void setLocation(Point p) { setLocation(p.x, p.y); } public void setLocation(int x, int y) { setBounds(x, y, getWidth(), getHeight()); } public void setBounds(Rectangle r) { setBounds(r.x, r.x, r.width, r.height); } public void setBounds(int x, int y, int width, int height) { mXOrig = x/mScaleFactor; mYOrig = y/mScaleFactor; if(Config.getRepInstance().getBool("GRID_SNAP")) { int grid_size = Config.getRepInstance().getInt("GRID_SIZE"); mXOrig = mXOrig-((mXOrig+mBodyXOffsetOrig)%grid_size); mYOrig = mYOrig-((mYOrig+mBodyYOffsetOrig)%grid_size); } mXScaled = mXOrig*mScaleFactor; mYScaled = mYOrig*mScaleFactor; super.setBounds((int)mXScaled, (int)mYScaled, width, height); } public int getX() { return (int)mXScaled; } public int getY() { return (int)mYScaled; } public Rectangle getBounds(Rectangle rv) { if(rv == null) { rv = new Rectangle(); } rv.x = getX(); rv.y = getY(); rv.width = getWidth(); rv.height = getHeight(); return rv; } public void setElementColor(Color bodyColor) { mTitleBackgroundColor = bodyColor; repaint(); } public void selectElement() { if(!mSelected) { mSelected = true; } } public void deselectElement() { if(mSelected) { mSelected = false; } } public boolean isSelected() { return mSelected; } public ElementStyle getElementStyleScaled() { return mElementStyleScaled; } protected void resetPrecalculatedAreas() { mWidthOrig = -1f; mHeightOrig = -1f; mBodyXOffsetOrig = -1f; mBodyYOffsetOrig = -1f; mBoundingOrig = null; mBodyAreaOutsideOrig = null; mBodyAreaInsideOrig = null; mUsedParameterLinesOrig = null; mWidthScaled = -1f; mHeightScaled = -1f; mBoundingScaled = null; mBodyAreaOutsideScaled = null; mBodyAreaInsideScaled = null; mUsedParameterLinesScaled = null; } ElementProperty createProperty(Class propertyClass, String name) { ElementProperty property = null; try { property = (ElementProperty)propertyClass.getConstructor(new Class[] {Element.class, String.class}).newInstance(new Object[] {this, name}); } catch (Throwable e) { JDialogSystemError dialog = new JDialogSystemError(Rife.getMainFrame(), "Element.createProperty() : Error while creating an element property with name '"+name+"' and class '"+ propertyClass.getName()+"': "+ExceptionUtils.getExceptionStackTrace(e)); dialog.setVisible(true); Rife.quit(); } return property; } ElementProperty getProperty(Class propertyClass, String name) { int index = mElementProperties.indexOf(createProperty(propertyClass, name)); if(index == -1) { return null; } else { return mElementProperties.get(index); } } ElementProperty addProperty(Class propertyClass, String name) { ElementProperty property = createProperty(propertyClass, name); if(property != null) { if(!mElementProperties.contains(property)) { mElementProperties.add(property); resetPrecalculatedAreas(); return property; } } return null; } boolean removeProperty(ElementProperty property) { if(mElementProperties.remove(property)) { resetPrecalculatedAreas(); return true; } else { return false; } } boolean renameProperty(ElementProperty property, String newName) { if(mElementProperties.contains(property)) { if(property.isValidName(newName)) { property.setName(newName); resetPrecalculatedAreas(); return true; } else { return false; } } return false; } ElementPropertyExit addExit(String name) { return (ElementPropertyExit)addProperty(ElementPropertyExit.class, name); } ElementPropertyParameterConsumed addConsumedParameter(String name) { return (ElementPropertyParameterConsumed)addProperty(ElementPropertyParameterConsumed.class, name); } ElementPropertyParameterUsed addUsedParameter(String name) { return (ElementPropertyParameterUsed)addProperty(ElementPropertyParameterUsed.class, name); } ElementPropertyParameterAdded addAddedParameter(String name) { return (ElementPropertyParameterAdded)addProperty(ElementPropertyParameterAdded.class, name); } ElementProperty getTitle() { return getProperties(ElementPropertyTitle.class).get(0); } ArrayList<ElementProperty> getExits() { return getProperties(ElementPropertyExit.class); } ArrayList<ElementProperty> getConsumedParameters() { return getProperties(ElementPropertyParameterConsumed.class); } ArrayList<ElementProperty> getUsedParameters() { return getProperties(ElementPropertyParameterUsed.class); } ArrayList<ElementProperty> getAddedParameters() { return getProperties(ElementPropertyParameterAdded.class); } ArrayList<ElementProperty> getProperties(Class propertyClass) { ArrayList<ElementProperty> result = new ArrayList<ElementProperty>(); for (ElementProperty property : mElementProperties) { if(propertyClass.isInstance(property)) { result.add(property); } } return result; } int countExits() { return countProperties(ElementPropertyExit.class); } int countConsumedParameters() { return countProperties(ElementPropertyParameterConsumed.class); } int countUsedParameters() { return countProperties(ElementPropertyParameterUsed.class); } int countAddedParameters() { return countProperties(ElementPropertyParameterAdded.class); } private int countProperties(Class propertyClass) { int result = 0; for (ElementProperty property : mElementProperties) { if(propertyClass.isInstance(property)) { result++; } } return result; } public Dimension getMinimumSize() { return new Dimension((int)mWidthScaled, (int)mHeightScaled); } public Dimension getPreferredSize() { return getMinimumSize(); } public Dimension getMaximumSize() { return getMinimumSize(); } public boolean contains(int x, int y) { if(mBoundingScaled != null&& x >= 0 && y >= 0 && x < mWidthScaled && y < mHeightScaled && mBoundingScaled.contains(x, y)) { return true; } else { return false; } } public synchronized void scalePrecalculatedAreas(float multiplier) { mScaleFactor *= multiplier; mXScaled = mXOrig * mScaleFactor; mYScaled = mYOrig * mScaleFactor; mWidthScaled = mWidthOrig * mScaleFactor; mHeightScaled = mHeightOrig * mScaleFactor; AffineTransform scale_transform = AffineTransform.getScaleInstance(mScaleFactor, mScaleFactor); mBoundingScaled = mBoundingOrig.createTransformedArea(scale_transform); mBodyAreaOutsideScaled = mBodyAreaOutsideOrig.createTransformedArea(scale_transform); mBodyAreaInsideScaled = mBodyAreaInsideOrig.createTransformedArea(scale_transform); mTitleRectangleOutsideScaled = mTitleRectangleOutsideOrig.createTransformedArea(scale_transform); mTitleRectangleInsideScaled = mTitleRectangleInsideOrig.createTransformedArea(scale_transform); mUsedParameterLinesScaled = (GeneralPath)mUsedParameterLinesOrig.clone(); mUsedParameterLinesScaled.transform(scale_transform); for (ElementProperty property : mElementProperties) { property.scale(multiplier); } super.setBounds((int)mXScaled, (int)mYScaled, (int)mWidthScaled, (int)mHeightScaled); } private synchronized void createPrecalculatedAreas() { int grid_size = Config.getRepInstance().getInt("GRID_SIZE"); LineMetrics line_metrics = null; Rectangle2D string_bounds = null; Rectangle2D name_bounds = null; Area hotspot = null; // // calculate parameters dimensions // line_metrics = mElementStyleOrig.mParamFont.getLineMetrics("M", mElementStyleOrig.mFontRenderContext); float parameter_text_width = (float)(mElementStyleOrig.mParamFont.getStringBounds("M", mElementStyleOrig.mFontRenderContext).getHeight()); float parameter_text_middle = line_metrics.getDescent()-line_metrics.getStrikethroughOffset(); // calculate the parameter rectangle width, this is the distance that one parameter needs to draw its components // and to be aligned to the grid float parameter_rectangle_width_minimum = parameter_text_width+ (mElementStyleOrig.mParamMarginWidth*2)+ mElementStyleOrig.mParamLineWidth; float parameter_rectangle_width_unit = grid_size*2; float parameter_rectangle_width = parameter_rectangle_width_unit; if(parameter_rectangle_width_minimum > parameter_rectangle_width_unit) { parameter_rectangle_width = (float)(Math.ceil(parameter_rectangle_width_minimum/parameter_rectangle_width_unit)*parameter_rectangle_width_unit); } // calculate to FINAL WIDTH of the complete consumed parameters rectangle float consumed_parameters_rectangle_width = 0; float number_of_consumed_parameters = countConsumedParameters(); if(number_of_consumed_parameters > 0) { consumed_parameters_rectangle_width = (number_of_consumed_parameters*parameter_rectangle_width)+ ((number_of_consumed_parameters+1)*(parameter_rectangle_width/2)); } // calculate to FINAL WIDTH of the complete used parameters rectangle float used_parameters_rectangle_width = 0; float number_of_used_parameters = countUsedParameters(); if(number_of_used_parameters > 0) { used_parameters_rectangle_width = (number_of_used_parameters*parameter_rectangle_width)+ ((number_of_used_parameters+1)*(parameter_rectangle_width/2)); if(number_of_consumed_parameters == 0) { used_parameters_rectangle_width += mElementStyleOrig.mElementBorderWidth; } } // calculate to FINAL WIDTH of the complete added parameters rectangle float added_parameters_rectangle_width = 0; float number_of_added_parameters = countAddedParameters(); if(number_of_added_parameters > 0) { added_parameters_rectangle_width = number_of_added_parameters*parameter_rectangle_width+ number_of_added_parameters*(parameter_rectangle_width/2); } // // calculate title dimensions // line_metrics = mElementStyleOrig.mTitleFont.getLineMetrics("M", mElementStyleOrig.mFontRenderContext); Rectangle2D name_text_bounds = mElementStyleOrig.mTitleFont.getStringBounds(getTitle().getName(), mElementStyleOrig.mFontRenderContext); float title_text_height = (float)(name_text_bounds.getHeight()); float title_text_width = (float)(name_text_bounds.getWidth()); // calculate the MINIMAL WIDTH of the TITLE RECTANGLE so that it contains at least the title text float name_rectangle_width = title_text_width+ mElementStyleOrig.mElementBorderWidth*2+ mElementStyleOrig.mTitleMarginWidth*2; // snap it to grid units name_rectangle_width = (float)(Math.ceil(name_rectangle_width/grid_size)*grid_size); // calculate the FINAL HEIGHT of the TITLE RECTANGLE float name_rectangle_height = title_text_height+ mElementStyleOrig.mElementBorderWidth*2+ mElementStyleOrig.mTitleMarginHeight*2; // snap it to grid units name_rectangle_height = (float)(Math.ceil(name_rectangle_height/grid_size)*grid_size); // calculate the temporary width of the complete element rectangle and take space for the right edge float element_rectangle_width = consumed_parameters_rectangle_width+ used_parameters_rectangle_width+ parameter_rectangle_width+ name_rectangle_width+ mElementStyleOrig.mElementBorderWidth; // calculate the FINAL X OFFSET to start the TITLE BOX immediatly right of the used parameters rectangle float name_rectangle_x_offset = element_rectangle_width-name_rectangle_width; // // make sure that the element is wide enough to contain all the parameters and exit widths // // calculate the maximum width of all the exit texts float exits_width = 0; float exit_string_width = 0; for (ElementProperty exit : getExits()) { exit_string_width = (float)(mElementStyleOrig.mExitFont.getStringBounds(exit.getName(), mElementStyleOrig.mFontRenderContext).getWidth()); if(exit_string_width > exits_width) { exits_width = exit_string_width; } } // calculate the minimal body rectangle width to known what size has to be at least available to display all the items // inside the body (consumed parameter texts, used parameter texts and lines, added parameters texts, exit texts) float minimal_body_rectangle_width = consumed_parameters_rectangle_width+ used_parameters_rectangle_width+ added_parameters_rectangle_width+ parameter_rectangle_width+ mElementStyleOrig.mExitMarginWidth+exits_width+mElementStyleOrig.mExitMarginWidth; // snap yhe minimal body rectangle width to grid units and take space for the right edge minimal_body_rectangle_width = (float)(Math.ceil(minimal_body_rectangle_width/grid_size)*grid_size)+mElementStyleOrig.mElementBorderWidth; if(element_rectangle_width < minimal_body_rectangle_width) { // calculate the FINAL WIDTH of the ELEMENT RECTANGLE element_rectangle_width = minimal_body_rectangle_width; // calculate the FINAL WIDTH of the TITLE RECTANGLE so that it's wide enough to fill all the space // right of the used parameters rectangle name_rectangle_width = element_rectangle_width- (consumed_parameters_rectangle_width+used_parameters_rectangle_width+parameter_rectangle_width+mElementStyleOrig.mElementBorderWidth); } // // calculate body dimensions // line_metrics = mElementStyleOrig.mExitFont.getLineMetrics("M", mElementStyleOrig.mFontRenderContext); float exit_text_height = (float)(mElementStyleOrig.mExitFont.getStringBounds("M", mElementStyleOrig.mFontRenderContext).getHeight()); // calculate the exit rectangle height, this is the distance that one exit needs to draw its text and rectangle float exit_rectangle_height = exit_text_height+ (mElementStyleOrig.mElementBorderWidth*2)+ (mElementStyleOrig.mExitMarginHeight*2); // snap it to grid units exit_rectangle_height = (float)(Math.ceil(exit_rectangle_height/grid_size)*grid_size); // calculate the width of the exit rectangle float exit_rectangle_width = grid_size*4+mElementStyleOrig.mElementBorderWidth; // calculate the vertical offset from one exit to the next one float exit_rectangle_y_interval = exit_rectangle_height+(exit_rectangle_height/2); // take space for the bottom edge exit_rectangle_height += mElementStyleOrig.mElementBorderWidth; // calculate the MINIMUM HEIGHT of the BODY RECTANGLE float body_rectangle_height = name_rectangle_height*2; // calculate the FINAL WIDTH of the BODY RECTANGLE float body_rectangle_width = element_rectangle_width; // calculate the temporary height of the body rectangle, this is large enough to display the title and all the exits float number_of_exits = countExits(); if(number_of_exits > 0) { body_rectangle_height = name_rectangle_height+ number_of_exits*exit_rectangle_y_interval+ exit_rectangle_y_interval/3; } // // make sure that the body is high enough to contain all the parameters in height // // calculate the maximum height of all the consumed parameter texts float parameters_height = 0; float parameter_string_width = 0; for (ElementProperty parameter : getConsumedParameters()) { parameter_string_width = (float)(mElementStyleOrig.mParamFont.getStringBounds(parameter.getName(), mElementStyleOrig.mFontRenderContext).getWidth()); if(parameter_string_width > parameters_height) { parameters_height = parameter_string_width; } } // calculate the minimal body rectangle height that is required to correctly display the longest consumed parameter float minimal_body_rectangle_height_for_parameters = mElementStyleOrig.mParamLineLength+ mElementStyleOrig.mParamMarginHeight+ parameters_height+ (mElementStyleOrig.mElementBorderWidth*2); // calculate the height of the body rectangle and make it at least as big as the previously calculated minimal body height if(body_rectangle_height < minimal_body_rectangle_height_for_parameters) { body_rectangle_height = minimal_body_rectangle_height_for_parameters; } // calculate the maximum height of all the used parameter texts parameters_height = 0; parameter_string_width = 0; for (ElementProperty parameter : getUsedParameters()) { parameter_string_width = (float)(mElementStyleOrig.mParamFont.getStringBounds(parameter.getName(), mElementStyleOrig.mFontRenderContext).getWidth()); if(parameter_string_width > parameters_height) { parameters_height = parameter_string_width; } } // calculate the minimal body rectangle height that is required to correctly display the longest used parameter minimal_body_rectangle_height_for_parameters = mElementStyleOrig.mParamLineLength+ mElementStyleOrig.mParamMarginHeight+ parameters_height+ (mElementStyleOrig.mElementBorderWidth*2); // calculate the height of the body rectangle and make it at least as big as the previously calculated minimal body height if(body_rectangle_height < minimal_body_rectangle_height_for_parameters) { body_rectangle_height = minimal_body_rectangle_height_for_parameters; } // calculate the maximum height of all the added parameter texts parameters_height = 0; for (ElementProperty parameter : getAddedParameters()) { parameter_string_width = (float)(mElementStyleOrig.mParamFont.getStringBounds(parameter.getName(), mElementStyleOrig.mFontRenderContext).getWidth()); if(parameter_string_width > parameters_height) { parameters_height = parameter_string_width; } } // calculate the minimal body rectangle height that is required to correctly display the longest added parameter minimal_body_rectangle_height_for_parameters = name_rectangle_height+ (mElementStyleOrig.mParamLineLength/2)+ parameters_height+ mElementStyleOrig.mParamMarginHeight+ (mElementStyleOrig.mElementBorderWidth*2); // calculate the height of the body rectangle and make it at least as big as the previously calculated minimal body height if(body_rectangle_height < minimal_body_rectangle_height_for_parameters) { body_rectangle_height = minimal_body_rectangle_height_for_parameters; } // snap the height of the body to grid units and take space for the bottom edge body_rectangle_height = (float)(Math.ceil(body_rectangle_height/grid_size)*grid_size)+mElementStyleOrig.mElementBorderWidth; // // calculate the width and height of the buffered image // mBodyXOffsetOrig = 0; mBodyYOffsetOrig = 0; // calculate the required vertical offset that is needed for the optional top parameter handles (consumed and used) if(number_of_consumed_parameters > 0 || number_of_used_parameters > 0) { mBodyYOffsetOrig += mElementStyleOrig.mParamLineLength/2; } // calculate the body width, taking into account the existance of exits if(number_of_exits > 0) { mWidthOrig = element_rectangle_width+exit_rectangle_width+1; } else { mWidthOrig = element_rectangle_width+1; } // calculate the body height, taking into account the space taken by optional parameter handles if(number_of_consumed_parameters > 0 || number_of_used_parameters > 0 || number_of_added_parameters > 0) { mHeightOrig = body_rectangle_height+mElementStyleOrig.mParamLineLength+1; } else { mHeightOrig = body_rectangle_height+1; } // // precalculate the body shape // // create the body area, including the outside stroke Area body_area_outside = new Area(new Rectangle2D.Float(mBodyXOffsetOrig, mBodyYOffsetOrig, body_rectangle_width, body_rectangle_height)); // create the body area, excluding the outside stroke Area body_area_inside = new Area(new Rectangle2D.Float(mBodyXOffsetOrig+mElementStyleOrig.mElementBorderWidth, mBodyYOffsetOrig+mElementStyleOrig.mElementBorderWidth, body_rectangle_width-mElementStyleOrig.mElementBorderWidth*2, body_rectangle_height-mElementStyleOrig.mElementBorderWidth*2)); // if there are exits, add them to the body shape if(number_of_exits > 0) { // create the base exit shape, including the outside stroke Area exit_area_outside = new Area( new Rectangle2D.Float(mBodyXOffsetOrig+element_rectangle_width-mElementStyleOrig.mElementBorderWidth, mBodyYOffsetOrig+name_rectangle_height+exit_rectangle_y_interval/3, exit_rectangle_width, exit_rectangle_height)); // create the base exit shape, excluding the outside stroke Area exit_area_inside = new Area( new Rectangle2D.Float(mBodyXOffsetOrig+element_rectangle_width-mElementStyleOrig.mElementBorderWidth, mBodyYOffsetOrig+name_rectangle_height+exit_rectangle_y_interval/3+mElementStyleOrig.mElementBorderWidth, exit_rectangle_width-mElementStyleOrig.mElementBorderWidth, exit_rectangle_height-mElementStyleOrig.mElementBorderWidth*2)); // create the transformation that will translate the previous exit shape to make it suit for the next one AffineTransform next_exit_transform = AffineTransform.getTranslateInstance(0, exit_rectangle_y_interval); // process each exit, add its shapes to the body shapes and transform the exit shapes for the optional next exit Rectangle2D exit_area_outside_bounds = null; float hotspot_width = exits_width+ mElementStyleOrig.mExitMarginWidth*2; if(hotspot_width < exit_rectangle_width) { hotspot_width = exit_rectangle_width; } float hotspot_x = mWidthOrig- mElementStyleOrig.mElementBorderWidth*2- hotspot_width; for (ElementProperty exit : getExits()) { body_area_outside.add(exit_area_outside); body_area_inside.add(exit_area_inside); exit_area_outside_bounds = exit_area_outside.getBounds2D(); exit.setHotSpot(new Rectangle2D.Float(hotspot_x, (float)exit_area_outside_bounds.getY(), hotspot_width, (float)exit_area_outside_bounds.getHeight())); exit_area_outside = exit_area_outside.createTransformedArea(next_exit_transform); exit_area_inside = exit_area_inside.createTransformedArea(next_exit_transform); } } // create the bounding area by stroking the body area mBoundingOrig = new Area((new BasicStroke(1)).createStrokedShape(body_area_outside)); mBoundingOrig.add(body_area_outside); // store the body areas and create the outside shape so that it only contains what is needed to draw the stroke mBodyAreaInsideOrig = body_area_inside; mBodyAreaOutsideOrig = (Area)body_area_outside.clone(); mBodyAreaOutsideOrig.subtract(mBodyAreaInsideOrig); // // precalculate exit name texts // if(number_of_exits > 0) { // calculate the base location for the first exit text float base_exit_text_x_offset = mBodyXOffsetOrig+element_rectangle_width+exit_rectangle_width-mElementStyleOrig.mExitMarginWidth*2-mElementStyleOrig.mElementBorderWidth; float base_exit_text_y_offset = mBodyYOffsetOrig+name_rectangle_height+exit_rectangle_y_interval/3+ mElementStyleOrig.mElementBorderWidth+mElementStyleOrig.mExitMarginHeight+exit_text_height; // iterate over all the exit texts for (ElementProperty exit : getExits()) { // store each exit text with it's precalculated location in a dedicated glyph vector string_bounds = mElementStyleOrig.mExitFont.getStringBounds(exit.getName(), mElementStyleOrig.mFontRenderContext); exit.setNameBounds(new Rectangle2D.Float((float)(base_exit_text_x_offset-string_bounds.getWidth()), (float)(base_exit_text_y_offset-string_bounds.getHeight()), (float)string_bounds.getWidth(), (float)string_bounds.getHeight())); // move the vertical position downwards base_exit_text_y_offset += exit_rectangle_y_interval; } } // // precalculate title shape // mTitleRectangleOutsideOrig = new Area(new Rectangle2D.Float(mBodyXOffsetOrig+name_rectangle_x_offset, mBodyYOffsetOrig, name_rectangle_width, name_rectangle_height)); mTitleRectangleInsideOrig = new Area(new Rectangle2D.Float(mBodyXOffsetOrig+name_rectangle_x_offset+mElementStyleOrig.mElementBorderWidth, mBodyYOffsetOrig+mElementStyleOrig.mElementBorderWidth, name_rectangle_width-mElementStyleOrig.mElementBorderWidth*2, name_rectangle_height-mElementStyleOrig.mElementBorderWidth*2)); mBodyAreaOutsideOrig.add(mTitleRectangleOutsideOrig); mBodyAreaOutsideOrig.subtract(mTitleRectangleInsideOrig); mBodyAreaInsideOrig.subtract(mTitleRectangleOutsideOrig); // // precalculate consumed parameters and texts // // set the initial horizontal offset for the drawing of the parameter handles, this is both used by all parameters float parameter_x_cursor = mBodyXOffsetOrig+(parameter_rectangle_width/2); if(number_of_consumed_parameters > 0) { // move the initial parameter line offset to the position for consumed parameters parameter_x_cursor += parameter_rectangle_width; // calculate the vertical start and end positions of the top parameter handles float consumed_parameter_handle_top_y1 = mBodyYOffsetOrig-(mElementStyleOrig.mParamLineLength/2); float consumed_parameter_handle_top_y2 = mBodyYOffsetOrig+(mElementStyleOrig.mParamLineLength/2); // create the first parameter handle area Area consumed_parameter_handle_top_area = new Area(new Rectangle2D.Float(parameter_x_cursor-(mElementStyleOrig.mParamLineWidth/2), consumed_parameter_handle_top_y1, mElementStyleOrig.mParamLineWidth, mElementStyleOrig.mParamLineLength)); // calculate the horizontal interval between the parameters and create the transform object to perform the translation float consumed_parameter_x_interval = parameter_rectangle_width+(parameter_rectangle_width/2); AffineTransform next_consumed_parameter_transform = AffineTransform.getTranslateInstance(consumed_parameter_x_interval, 0); // process all the consumed parameters for (ElementProperty parameter : getConsumedParameters()) { // add the consumed parameter handle to the body area and bounding area mBodyAreaOutsideOrig.add(consumed_parameter_handle_top_area); mBoundingOrig.add(consumed_parameter_handle_top_area); // add the consumed parameter texts together with their locations as glyph vectors to the collection of parameter glyph vectors string_bounds = mElementStyleOrig.mParamFont.getStringBounds(parameter.getName(), mElementStyleOrig.mFontRenderContext); name_bounds = new Rectangle2D.Float((float)(parameter_x_cursor+parameter_text_middle-string_bounds.getHeight()), consumed_parameter_handle_top_y2+mElementStyleOrig.mParamMarginHeight, (float)string_bounds.getHeight(), (float)string_bounds.getWidth()); parameter.setNameBounds(name_bounds); hotspot = new Area(new Rectangle2D.Float((float)name_bounds.getX(), consumed_parameter_handle_top_y1, (float)name_bounds.getWidth(), (float)(name_bounds.getY()+name_bounds.getHeight()))); hotspot.intersect(mBoundingOrig); parameter.setHotSpot(hotspot); // update the horizontal offset and translate the parameter handle area parameter_x_cursor += consumed_parameter_x_interval; consumed_parameter_handle_top_area = consumed_parameter_handle_top_area.createTransformedArea(next_consumed_parameter_transform); } } else { // adapt the initial horizontal offset in case there are no consumed parameters to make the used parameters appear // on the correct location parameter_x_cursor += parameter_rectangle_width/2; } // // precalculate used parameters lines and texts // mUsedParameterLinesOrig = new GeneralPath(); if(number_of_used_parameters > 0) { // move the initial parameter line offset to the position for used parameters parameter_x_cursor += parameter_rectangle_width/2; // calculate the vertical start and end positions of the top and bottom parameter handles float used_parameter_handle_top_y1 = mBodyYOffsetOrig-(mElementStyleOrig.mParamLineLength/2); float used_parameter_handle_top_y2 = mBodyYOffsetOrig+(mElementStyleOrig.mParamLineLength/2); float used_parameter_handle_bottom_y1 = used_parameter_handle_top_y1+body_rectangle_height; float used_parameter_handle_bottom_y2 = used_parameter_handle_top_y2+body_rectangle_height; // create the first parameter top and bottom handle areas with their connection line Area used_parameter_handle_top_area = new Area(new Rectangle2D.Float(parameter_x_cursor-(mElementStyleOrig.mParamLineWidth/2), used_parameter_handle_top_y1, mElementStyleOrig.mParamLineWidth, mElementStyleOrig.mParamLineLength)); Area used_parameter_handle_bottom_area = new Area(new Rectangle2D.Float(parameter_x_cursor-(mElementStyleOrig.mParamLineWidth/2), used_parameter_handle_bottom_y1, mElementStyleOrig.mParamLineWidth, mElementStyleOrig.mParamLineLength)); Line2D used_parameter_handle_connect = new Line2D.Float(parameter_x_cursor, used_parameter_handle_top_y2, parameter_x_cursor, used_parameter_handle_bottom_y1); // calculate the horizontal interval between the parameters and create the transform object to perform the translation float used_parameter_x_interval = parameter_rectangle_width+(parameter_rectangle_width/2); AffineTransform next_used_parameter_transform = AffineTransform.getTranslateInstance(used_parameter_x_interval, 0); // process all the used parameters for (ElementProperty parameter : getUsedParameters()) { // add the used parameter top and bottom handles to the body area and bounding area mBodyAreaOutsideOrig.add(used_parameter_handle_top_area); mBodyAreaOutsideOrig.add(used_parameter_handle_bottom_area); mBoundingOrig.add(used_parameter_handle_top_area); mBoundingOrig.add(used_parameter_handle_bottom_area); // add the connection line between handles to the collection of used parameter connection lines mUsedParameterLinesOrig.moveTo(parameter_x_cursor, used_parameter_handle_top_y2); mUsedParameterLinesOrig.lineTo(parameter_x_cursor, used_parameter_handle_bottom_y1); // add the used parameter texts together with their locations as glyph vectors to the collection of parameter glyph vectors string_bounds = mElementStyleOrig.mParamFont.getStringBounds(parameter.getName(), mElementStyleOrig.mFontRenderContext); name_bounds = new Rectangle2D.Float((float)(parameter_x_cursor-mElementStyleOrig.mParamMarginWidth-string_bounds.getHeight()), used_parameter_handle_top_y2+mElementStyleOrig.mParamMarginHeight, (float)string_bounds.getHeight(), (float)string_bounds.getWidth()); parameter.setNameBounds(name_bounds); hotspot = new Area(new Rectangle2D.Float((float)name_bounds.getX(), used_parameter_handle_top_y1, (float)(parameter_x_cursor+mElementStyleOrig.mParamLineWidth-name_bounds.getX()), used_parameter_handle_bottom_y2-used_parameter_handle_top_y1)); hotspot.intersect(mBoundingOrig); parameter.setHotSpot(hotspot); // update the horizontal offset and translate the parameter top and bottom handle areas and their connection line parameter_x_cursor += used_parameter_x_interval; used_parameter_handle_top_area = used_parameter_handle_top_area.createTransformedArea(next_used_parameter_transform); used_parameter_handle_bottom_area = used_parameter_handle_bottom_area.createTransformedArea(next_used_parameter_transform); used_parameter_handle_connect.setLine(parameter_x_cursor, used_parameter_handle_top_y2, parameter_x_cursor, used_parameter_handle_bottom_y1); } } // // precalculate added parameters lines and texts // if(number_of_added_parameters > 0) { // move the initial parameter line offset to the position for added parameters parameter_x_cursor += parameter_rectangle_width/2; // calculate the vertical start and end positions of the bottom parameter handles float added_parameter_line_y1 = mBodyYOffsetOrig+body_rectangle_height-(mElementStyleOrig.mParamLineLength/2); float added_parameter_line_y2 = mBodyYOffsetOrig+body_rectangle_height+(mElementStyleOrig.mParamLineLength/2); // create the first parameter handle area Area added_parameter_line_area = new Area(new Rectangle2D.Float(parameter_x_cursor-(mElementStyleOrig.mParamLineWidth/2), added_parameter_line_y1, mElementStyleOrig.mParamLineWidth, mElementStyleOrig.mParamLineLength)); // calculate the horizontal interval between the parameters and create the transform object to perform the translation float added_parameter_x_interval = parameter_rectangle_width+(parameter_rectangle_width/2); AffineTransform next_added_parameter_transform = AffineTransform.getTranslateInstance(added_parameter_x_interval, 0); // process all the added parameters for (ElementProperty parameter : getAddedParameters()) { // add the added parameter handle to the body area and bounding area mBodyAreaOutsideOrig.add(added_parameter_line_area); mBoundingOrig.add(added_parameter_line_area); // add the added parameter texts together with their locations as glyph vectors to the collection of parameter glyph vectors string_bounds = mElementStyleOrig.mParamFont.getStringBounds(parameter.getName(), mElementStyleOrig.mFontRenderContext); name_bounds = new Rectangle2D.Float((float)(parameter_x_cursor+parameter_text_middle-string_bounds.getHeight()), (float)(added_parameter_line_y1-mElementStyleOrig.mParamMarginHeight-string_bounds.getWidth()), (float)string_bounds.getHeight(), (float)string_bounds.getWidth()); parameter.setNameBounds(name_bounds); hotspot = new Area(new Rectangle2D.Float((float)name_bounds.getX(), (float)name_bounds.getY(), (float)name_bounds.getWidth(), (float)(added_parameter_line_y2-name_bounds.getY()))); hotspot.intersect(mBoundingOrig); parameter.setHotSpot(hotspot); // update the horizontal offset and translate the parameter handle area parameter_x_cursor += added_parameter_x_interval; added_parameter_line_area = added_parameter_line_area.createTransformedArea(next_added_parameter_transform); } } // // precalculate name text // ElementProperty title = getTitle(); title.setNameBounds(new Rectangle2D.Float(mBodyXOffsetOrig+name_rectangle_x_offset+(name_rectangle_width-title_text_width)/2, mBodyYOffsetOrig+mElementStyleOrig.mTitleMarginHeight+mElementStyleOrig.mElementBorderWidth, title_text_width, title_text_height)); title.setHotSpot(mTitleRectangleOutsideOrig); // // create the scaled version, keeping the current scale factor // scalePrecalculatedAreas(1); } public void drawElement(Graphics2D g2d) { Rectangle clip = g2d.getClipBounds(); if(mBoundingScaled == null) { createPrecalculatedAreas(); } if(clip == null || mBoundingScaled.intersects(clip)) { boolean drag_outline = Config.getRepInstance().getBool("DRAG_OUTLINE"); boolean scrolling_fast = Config.getRepInstance().getBool("SCROLLING_FAST"); boolean scrolling_outline = Config.getRepInstance().getBool("SCROLLING_OUTLINE"); boolean scroll_active = ((StructurePanel)getParent()).isScrollActive(); if((!mDragActive && !scroll_active) || (mDragActive && !drag_outline) || (scroll_active && !scrolling_outline)) { if(!mSelected) { g2d.setColor(mElementStyleScaled.mBodyBackgroundColor); } else { g2d.setColor(mElementStyleScaled.mBodyBackgroundColorSelected); } g2d.fill(mBodyAreaInsideScaled); } g2d.setColor(mElementStyleScaled.mElementBorderColor); g2d.fill(mBodyAreaOutsideScaled); if((!mDragActive && !scroll_active) || (mDragActive && !drag_outline) || (scroll_active && !scrolling_outline)) { if(!scrolling_fast || !scroll_active) { g2d.setColor(mElementStyleScaled.mElementBorderColor); g2d.setStroke(mElementStyleScaled.mParamLineDashedStroke); g2d.draw(mUsedParameterLinesScaled); } if(clip == null || mTitleRectangleInsideScaled.intersects(clip)) { g2d.setColor(mTitleBackgroundColor); g2d.fill(mTitleRectangleInsideScaled); } getTitle().draw(g2d); if(!scrolling_fast || !scroll_active) { for (ElementProperty property : mElementProperties) { if (!(property instanceof ElementPropertyTitle)) { property.draw(g2d); } } } mStructurePanel.drawHighlightedProperty(this, g2d); } } } public void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2d = (Graphics2D)g; if(mDragBufferedImage == null) { g2d = (Graphics2D)g2d.create(); ElementStyle.setRenderingHints(g2d, mScaleFactor); drawElement(g2d); } else { if(mIsTransparent) { g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f)); } g2d.drawImage(mDragBufferedImage, null, 0, 0); } } public BufferedImage getDragBufferedImage() { return mDragBufferedImage; } public void addElementListener(ElementListener listener) { mElementListeners.add(listener); } public void removeElementListener(ElementListener listener) { mElementListeners.remove(listener); } protected void fireElementRepositioned() { for (ElementListener listener : mElementListeners) { listener.elementRepositioned(this); } } protected void fireElementRaised() { for (ElementListener listener : mElementListeners) { listener.elementRaised(this); } } protected void fireElementSelected(int modifiers) { for (ElementListener listener : mElementListeners) { listener.elementSelected(this, modifiers); } } protected void fireElementDeselected(int modifiers) { for (ElementListener listener : mElementListeners) { listener.elementDeselected(this, modifiers); } } protected void fireElementDragged(int x, int y) { for (ElementListener listener : mElementListeners) { listener.elementDragged(this, x, y); } } protected void fireElementDragStart(Point dragStartPoint) { for (ElementListener listener : mElementListeners) { listener.elementDragStart(this, dragStartPoint); } } protected void fireElementDragEnd() { for (ElementListener listener : mElementListeners) { listener.elementDragEnd(); } } protected void fireElementPropertyHighlighted(ElementProperty property) { for (ElementListener listener : mElementListeners) { listener.elementPropertyHighlighted(property); } } protected void repositionElementDuringDrag(int offsetX, int offsetY) { setLocation(offsetX+getX(), offsetY+getY()); } protected void startDrag() { mDragActive = true; mDragBufferedImage = new BufferedImage((int)mWidthScaled, (int)mHeightScaled, BufferedImage.TYPE_INT_ARGB); Graphics2D g2d = mDragBufferedImage.createGraphics(); ElementStyle.setRenderingHints(g2d, mScaleFactor); drawElement(g2d); fireElementRaised(); if(Config.getRepInstance().getBool("DRAG_TRANSPARENT")) { mIsTransparent = true; } } protected void endDrag() { mDragBufferedImage = null; mDragActive = false; if(Config.getRepInstance().getBool("DRAG_TRANSPARENT")) { mIsTransparent = false; } fireElementRepositioned(); } private ElementProperty locateElementProperty(Point location) { Shape hotspot = null; for (ElementProperty property : mElementProperties) { hotspot = property.getHotSpot(); if(hotspot.contains(location)) { return property; } } return null; } private void triggerPopup(Point location) { fireElementRaised(); ElementProperty property = locateElementProperty(location); if(property != null) { property.showPopupMenu(location); } else { JPopupMenu popup = new JPopupMenu(); DynamicMenuBuilder menu_builder = new DynamicMenuBuilder(); JMenu menu_add = menu_builder.addMenu(popup, Localization.getString("rife.element.popupmenu.add"), Localization.getChar("rife.element.popupmenu.add.mnemonic")); menu_builder.addMenuItem(menu_add, Localization.getString("rife.element.popupmenu.add.exit"), new AddExit(location), Localization.getChar("rife.element.popupmenu.add.exit.mnemonic")); menu_builder.addMenuItem(menu_add, Localization.getString("rife.element.popupmenu.add.consumedparameter"), new AddConsumedParameter(location), Localization.getChar("rife.element.popupmenu.add.consumedparameter.mnemonic")); menu_builder.addMenuItem(menu_add, Localization.getString("rife.element.popupmenu.add.usedparameter"), new AddUsedParameter(location), Localization.getChar("rife.element.popupmenu.add.usedparameter.mnemonic")); menu_builder.addMenuItem(menu_add, Localization.getString("rife.element.popupmenu.add.addedparameter"), new AddAddedParameter(location), Localization.getChar("rife.element.popupmenu.add.addedparameter.mnemonic")); menu_builder.addMenuItem(popup, Localization.getString("rife.element.popupmenu.delete"), new Delete(), Localization.getChar("rife.element.popupmenu.delete.mnemonic")); popup.show(this, location.x, location.y); popup.addPopupMenuListener(getStructurePanel()); } } protected class AddExit implements DynamicMenuAction { private Point mClickedPoint = null; public AddExit(Point clickedPoint) { mClickedPoint = clickedPoint; } public void execute(JMenuItem menuItem) { ElementPropertyExit exit = addExit(""); createPrecalculatedAreas(); getStructurePanel().editElementProperty(exit, mClickedPoint); } } protected class AddConsumedParameter implements DynamicMenuAction { private Point mClickedPoint = null; public AddConsumedParameter(Point clickedPoint) { mClickedPoint = clickedPoint; } public void execute(JMenuItem menuItem) { ElementPropertyParameterConsumed parameter = addConsumedParameter(""); createPrecalculatedAreas(); getStructurePanel().editElementProperty(parameter, mClickedPoint); } } protected class AddUsedParameter implements DynamicMenuAction { private Point mClickedPoint = null; public AddUsedParameter(Point clickedPoint) { mClickedPoint = clickedPoint; } public void execute(JMenuItem menuItem) { ElementPropertyParameterUsed parameter = addUsedParameter(""); createPrecalculatedAreas(); getStructurePanel().editElementProperty(parameter, mClickedPoint); } } protected class AddAddedParameter implements DynamicMenuAction { private Point mClickedPoint = null; public AddAddedParameter(Point clickedPoint) { mClickedPoint = clickedPoint; } public void execute(JMenuItem menuItem) { ElementPropertyParameterAdded parameter = addAddedParameter(""); createPrecalculatedAreas(); getStructurePanel().editElementProperty(parameter, mClickedPoint); } } protected class Delete implements DynamicMenuAction { public void execute(JMenuItem menuItem) { JComponent parent = (JComponent)Element.this.getParent(); if(parent instanceof StructurePanel) { ((StructurePanel)parent).removeElement(Element.this); } } } private void updateHighlightedProperty(Point location) { ElementProperty property = locateElementProperty(location); fireElementPropertyHighlighted(property); } public void mouseClicked(MouseEvent e) { if((e.getModifiers() & MouseEvent.BUTTON1_MASK) != 0) { if(e.getClickCount() == 2) { ElementProperty property = locateElementProperty(e.getPoint()); if(property != null) { getStructurePanel().editElementProperty(property, e.getPoint()); } } } } public void mousePressed(MouseEvent e) { getStructurePanel().removeElementPropertyEditor(); if(e.isPopupTrigger()) { triggerPopup(e.getPoint()); } else if((e.getModifiers() & MouseEvent.BUTTON1_MASK) != 0) { mDragStartPoint = e.getPoint(); if(!mSelected) { fireElementSelected(e.getModifiers()); } else { fireElementDeselected(e.getModifiers()); } } } public void mouseReleased(MouseEvent e) { if(e.isPopupTrigger()) { triggerPopup(e.getPoint()); } else if((e.getModifiers() & MouseEvent.BUTTON1_MASK) != 0) { mDragStartPoint = null; if(mDragActive) { fireElementDragEnd(); } } } public void mouseEntered(MouseEvent e) { updateHighlightedProperty(e.getPoint()); } public void mouseExited(MouseEvent e) { if(!mStructurePanel.isDragActive() && !mStructurePanel.isElementPropertyBeingEdited() && !mStructurePanel.isPopupMenuActive()) { updateHighlightedProperty(new Point(-1, -1)); } if(mDragActive) { fireElementDragged(e.getPoint().x, e.getPoint().y); } } public void mouseDragged(MouseEvent e) { if(e.getComponent() == this) { if((e.getModifiers() & MouseEvent.BUTTON1_MASK) != 0) { if(mSelected) { if(!mDragActive) { fireElementDragStart(mDragStartPoint); } else { fireElementDragged(e.getPoint().x, e.getPoint().y); } } } } } public void mouseMoved(MouseEvent e) { updateHighlightedProperty(e.getPoint()); } }