/* $Id: StylePanelFig.java 18555 2010-07-24 10:01:49Z bobtarling $ ***************************************************************************** * Copyright (c) 2009 Contributors - see below * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * tfmorris ***************************************************************************** * * Some portions of this file was previously release using the BSD License: */ // Copyright (c) 2003-2009 The Regents of the University of California. All // Rights Reserved. Permission to use, copy, modify, and distribute this // software and its documentation without fee, and without a written // agreement is hereby granted, provided that the above copyright notice // and this paragraph appear in all copies. This software program and // documentation are copyrighted by The Regents of the University of // California. The software program and documentation are supplied "AS // IS", without any accompanying services from The Regents. The Regents // does not warrant that the operation of the program will be // uninterrupted or error-free. The end-user understands that the program // was developed for research purposes and is advised not to rely // exclusively on the program for any reason. IN NO EVENT SHALL THE // UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, // SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, // ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF // THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF // SUCH DAMAGE. THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY // WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE // PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF // CALIFORNIA HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, // UPDATES, ENHANCEMENTS, OR MODIFICATIONS. package org.argouml.ui; import java.awt.Color; import java.awt.Rectangle; import java.awt.event.FocusEvent; import java.awt.event.FocusListener; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import javax.swing.DefaultComboBoxModel; import javax.swing.JColorChooser; import javax.swing.JComboBox; import javax.swing.JLabel; import javax.swing.JTextField; import javax.swing.text.Document; import org.apache.log4j.Logger; import org.argouml.i18n.Translator; import org.argouml.swingext.SpacerPanel; import org.argouml.ui.targetmanager.TargetManager; import org.argouml.uml.diagram.ArgoDiagram; import org.argouml.uml.diagram.DiagramUtils; import org.argouml.uml.diagram.DiagramSettings.StereotypeStyle; import org.argouml.uml.diagram.ui.ArgoFig; import org.argouml.uml.diagram.ui.FigEdgeModelElement; import org.argouml.uml.diagram.ui.FigNodeModelElement; import org.argouml.uml.diagram.ui.StereotypeStyled; import org.argouml.util.ArgoFrame; import org.tigris.gef.presentation.Fig; import org.tigris.gef.ui.ColorRenderer; /** * The basic stylepanel for a Fig which allows the user to see and adjust * the common attributes of a Fig: * the boundaries box, * line and fill color information * and the stereotype view combo box. <p> * * Shown to the user as the "Presentation" tab. */ public class StylePanelFig extends StylePanel implements ItemListener, FocusListener, KeyListener { private static final Logger LOG = Logger.getLogger(StylePanelFig.class); private static final String CUSTOM_ITEM = Translator.localize("label.stylepane.custom") + "..."; private JLabel bboxLabel = new JLabel(Translator.localize("label.stylepane.bounds") + ": "); private JTextField bboxField = new JTextField(); private JLabel fillLabel = new JLabel(Translator.localize("label.stylepane.fill") + ": "); private JComboBox fillField = new JComboBox(); private JLabel lineLabel = new JLabel(Translator.localize("label.stylepane.line") + ": "); private JComboBox lineField = new JComboBox(); private JLabel stereoLabel = new JLabel(Translator.localize("menu.popup.stereotype-view") + ": "); private JComboBox stereoField = new JComboBox(); private SpacerPanel spacer = new SpacerPanel(); private SpacerPanel spacer2 = new SpacerPanel(); private SpacerPanel spacer3 = new SpacerPanel(); /** * The constructor of the style panel of a Fig. * This constructor does not create any contents of the panel. * * @param title the title string */ public StylePanelFig(String title) { super(title); } /** * The constructor. * */ public StylePanelFig() { super("Fig Appearance"); initChoices(); Document bboxDoc = bboxField.getDocument(); bboxDoc.addDocumentListener(this); bboxField.addKeyListener(this); bboxField.addFocusListener(this); fillField.addItemListener(this); lineField.addItemListener(this); stereoField.addItemListener(this); fillField.setRenderer(new ColorRenderer()); lineField.setRenderer(new ColorRenderer()); bboxLabel.setLabelFor(bboxField); add(bboxLabel); add(bboxField); fillLabel.setLabelFor(fillField); add(fillLabel); add(fillField); lineLabel.setLabelFor(lineField); add(lineLabel); add(lineField); stereoLabel.setLabelFor(stereoField); add(stereoLabel); add(stereoField); } /** * Fill in the user-choices. */ protected void initChoices() { fillField.addItem(Translator.localize("label.stylepane.no-fill")); fillField.addItem(Color.black); fillField.addItem(Color.white); fillField.addItem(Color.gray); fillField.addItem(Color.lightGray); fillField.addItem(Color.darkGray); fillField.addItem(new Color(255, 255, 200)); fillField.addItem(new Color(255, 200, 255)); fillField.addItem(new Color(200, 255, 255)); fillField.addItem(new Color(200, 200, 255)); fillField.addItem(new Color(200, 255, 200)); fillField.addItem(new Color(255, 200, 200)); fillField.addItem(new Color(200, 200, 200)); fillField.addItem(Color.red); fillField.addItem(Color.blue); fillField.addItem(Color.cyan); fillField.addItem(Color.yellow); fillField.addItem(Color.magenta); fillField.addItem(Color.green); fillField.addItem(Color.orange); fillField.addItem(Color.pink); fillField.addItem(CUSTOM_ITEM); lineField.addItem(Translator.localize("label.stylepane.no-line")); lineField.addItem(Color.black); lineField.addItem(Color.white); lineField.addItem(Color.gray); lineField.addItem(Color.lightGray); lineField.addItem(Color.darkGray); lineField.addItem(new Color(60, 60, 200)); lineField.addItem(new Color(60, 200, 60)); lineField.addItem(new Color(200, 60, 60)); lineField.addItem(Color.red); lineField.addItem(Color.blue); lineField.addItem(Color.cyan); lineField.addItem(Color.yellow); lineField.addItem(Color.magenta); lineField.addItem(Color.green); lineField.addItem(Color.orange); lineField.addItem(Color.pink); lineField.addItem(CUSTOM_ITEM); DefaultComboBoxModel model = new DefaultComboBoxModel(); stereoField.setModel(model); // NOTE: These must stay in this order to match rendering styles model.addElement(Translator .localize("menu.popup.stereotype-view.textual")); model.addElement(Translator .localize("menu.popup.stereotype-view.big-icon")); model.addElement(Translator .localize("menu.popup.stereotype-view.small-icon")); } /** * set whether this Fig has a editable boundingbox. This is done normally in * <code>refresh()</code>, e.g. for FigEdgeModelElements where it does * not make sense to edit the bounding box. * * @param value * the boolean value of the bounding box property */ protected void hasEditableBoundingBox(boolean value) { bboxField.setEnabled(value); bboxLabel.setEnabled(value); } /** * Handle a refresh of the style panel after the fig has moved.<p> * * <em>Warning</em>. There is a circular trap here. Editing the * boundary box will also trigger a refresh, and so we reset the * boundary box, which causes funny behaviour (the cursor keeps * jumping to the end of the text). * * The solution is to not reset the boundary box field if the boundaries * have not changed.<p> */ public void refresh() { if (TargetManager.getInstance().getTargets().size() > 1) { // See issue 6109 - if we have multiple targets this method // can result in a feedback problem where selecting a target // changes the selection colour in the combo and as a result // that trigger a change of colour of all selected Figs return; } Fig target = getPanelTarget(); //TODO: How about FigAssociationClass? if (target instanceof FigEdgeModelElement) { hasEditableBoundingBox(false); } else { hasEditableBoundingBox(true); } if (target == null) { return; } // The boundary box as held in the target fig, and as listed in // the // boundary box style field (null if we don't have anything // valid) Rectangle figBounds = target.getBounds(); Rectangle styleBounds = parseBBox(); // Only reset the text if the two are not the same (i.e the fig // has // moved, rather than we've just edited the text, when // setTargetBBox() // will have made them the same). Note that styleBounds could // be null, // so we do the test this way round. if (!(figBounds.equals(styleBounds))) { bboxField.setText(figBounds.x + "," + figBounds.y + "," + figBounds.width + "," + figBounds.height); } // Change the fill colour if (target.isFilled()) { Color c = target.getFillColor(); fillField.setSelectedItem(c); if (c != null && !fillField.getSelectedItem().equals(c)) { fillField.insertItemAt(c, fillField.getItemCount() - 1); fillField.setSelectedItem(c); } } else { fillField.setSelectedIndex(0); } // Change the line colour if (target.getLineWidth() > 0) { Color c = target.getLineColor(); // TODO: This is going to cause the color to be reset on a refresh lineField.setSelectedItem(c); if (c != null && !lineField.getSelectedItem().equals(c)) { lineField.insertItemAt(c, lineField.getItemCount() - 1); lineField.setSelectedItem(c); } } else { lineField.setSelectedIndex(0); } stereoField.setEnabled(target instanceof StereotypeStyled); stereoLabel.setEnabled(target instanceof StereotypeStyled); if (target instanceof StereotypeStyled) { StereotypeStyled fig = (StereotypeStyled) target; stereoField.setSelectedIndex(fig.getStereotypeStyle().ordinal()); } } /** * Change the bounds of the target fig. Called whenever the bounds box is * edited. <p> * * Format of the bounds is four integers representing x, y, width and height * separated by spaces or commas. An empty field is treated as no change and * leading and trailing spaces are ignored. <p> * * <em>Note</em>. There is a note in the old code that more work might be * needed, because this could change the graph model. I don't see how that * could ever be. */ protected void setTargetBBox() { Fig target = getPanelTarget(); // Can't do anything if we don't have a fig. if (target == null) { return; } // Parse the boundary box text. Null is // returned if it is empty or // invalid, which causes no change. Otherwise we tell // GEF we are making // a change, make the change and tell GEF we've // finished. Rectangle bounds = parseBBox(); if (bounds == null) { return; } if (!target.getBounds().equals(bounds)) { target.setBounds(bounds.x, bounds.y, bounds.width, bounds.height); target.endTrans(); } } /** * Parse the boundary box string and return the rectangle it * represents.<p> * * The syntax are four integers separated by spaces or commas. We * ignore leading and trailing blanks.<p> * * If we have the empty string we return <code>null</code>.<p> * * If we fail to parse, then we return <code>null</code> and print * out a rude message.<p> * * @return The size of the box, or <code>null</code> if the bounds string * is empty or invalid. */ protected Rectangle parseBBox() { Fig target = getPanelTarget(); // Get the text in the field, and don't do anything if the // field is // empty. String bboxStr = bboxField.getText().trim(); if (bboxStr.length() == 0) { return null; } // Parse the string as if // possible Rectangle res = new Rectangle(); java.util.StringTokenizer st = new java.util.StringTokenizer(bboxStr, ", "); try { boolean changed = false; if (!st.hasMoreTokens()) { return target.getBounds(); } res.x = Integer.parseInt(st.nextToken()); if (!st.hasMoreTokens()) { res.y = target.getBounds().y; res.width = target.getBounds().width; res.height = target.getBounds().height; return res; } res.y = Integer.parseInt(st.nextToken()); if (!st.hasMoreTokens()) { res.width = target.getBounds().width; res.height = target.getBounds().height; return res; } res.width = Integer.parseInt(st.nextToken()); // TODO: What does the magic number 6000 represent here? // Put it in an explanatory literal! - tfm - 20071205 if ((res.width + res.x) > 6000) { res.width = 6000 - res.x; changed = true; } if (!st.hasMoreTokens()) { res.width = target.getBounds().width; return res; } res.height = Integer.parseInt(st.nextToken()); // TODO: What does the magic number 6000 represent here? // Put it in an explanatory literal! - tfm - 20071205 if ((res.height + res.y) > 6000) { res.height = 6000 - res.y; changed = true; } if (res.x < 0 || res.y < 0) { // TODO: This exception will be thrown during autoscrolling // when the edge of the canvas is reached causing either // the width or height to be "adjusted" to a negative value LOG.warn("Part of bounding box is off screen " + res); } if (res.width < 0 || res.height < 0) { // TODO: This exception will be thrown during autoscrolling // when the edge of the canvas is reached causing either // the width or height to be "adjusted" to a negative value throw new IllegalArgumentException( "Bounding box has negative size " + res); } if (changed) { StringBuffer sb = new StringBuffer(); sb.append(Integer.toString(res.x)); sb.append(","); sb.append(Integer.toString(res.y)); sb.append(","); sb.append(Integer.toString(res.width)); sb.append(","); sb.append(Integer.toString(res.height)); bboxField.setText(sb.toString()); } } catch (NumberFormatException ex) { bboxField.setBackground(Color.RED); return null; } catch (IllegalArgumentException iae) { bboxField.setBackground(Color.RED); return null; } bboxField.setBackground(null); return res; } /** * Prompts the user for a new custom color and adds that color to the combo * box. * * @param field the combobox to enter a new color for * @param title the i18n key for the title for the dialog box * @param targetColor the initial Color set when the color-chooser is shown */ protected void handleCustomColor(JComboBox field, String title, Color targetColor) { Color newColor = JColorChooser.showDialog(ArgoFrame.getFrame(), Translator.localize(title), targetColor); if (newColor != null) { field.insertItemAt(newColor, field.getItemCount() - 1); field.setSelectedItem(newColor); } else if (getPanelTarget() != null) { field.setSelectedItem(targetColor); } } /** * Change the fill. */ public void setTargetFill() { Fig target = getPanelTarget(); Object c = fillField.getSelectedItem(); if (target == null || c == null) { return; } Boolean isColor = (c instanceof Color); if (isColor) { target.setFillColor((Color) c); } target.setFilled(isColor); target.endTrans(); // TODO: The following handling of multiselection is just a local // solution for the fill color, better find a more general solution: // (I don't know if it's undoable this way - thn) ArgoDiagram activeDiagram = DiagramUtils.getActiveDiagram(); for (Object t : TargetManager.getInstance().getTargets()) { Fig fig = null; if (t instanceof FigNodeModelElement) { fig = (Fig) t; } else { fig = activeDiagram.presentationFor(t); } if (fig != null && fig != target) { if (isColor) { fig.setFillColor((Color) c); } fig.setFilled(isColor); fig.endTrans(); } } } /** * Change the line. */ public void setTargetLine() { Fig target = getPanelTarget(); Object c = lineField.getSelectedItem(); if (target == null || c == null) { return; } Boolean isColor = (c instanceof Color); if (isColor) { target.setLineColor((Color) c); } target.setLineWidth(isColor ? ArgoFig.LINE_WIDTH : 0); target.endTrans(); // TODO: The following handling of multiselection is just a local // solution for the line color, better find a more general solution: // (I don't know if it's undoable this way - thn) ArgoDiagram activeDiagram = DiagramUtils.getActiveDiagram(); for (Object t : TargetManager.getInstance().getTargets()) { Fig fig = null; if (t instanceof FigNodeModelElement) { fig = (Fig) t; } else { fig = activeDiagram.presentationFor(t); } if (fig != null && fig != target) { if (isColor) { fig.setLineColor((Color) c); } fig.setLineWidth(isColor ? ArgoFig.LINE_WIDTH : 0); fig.endTrans(); } } } /* * @see java.awt.event.ItemListener#itemStateChanged(java.awt.event.ItemEvent) */ public void itemStateChanged(ItemEvent e) { Object src = e.getSource(); Fig target = getPanelTarget(); if (e.getStateChange() == ItemEvent.SELECTED && target != null) { if (src == fillField) { if (e.getItem() == CUSTOM_ITEM) { handleCustomColor(fillField, "label.stylepane.custom-fill-color", target.getFillColor()); } setTargetFill(); } else if (src == lineField) { if (e.getItem() == CUSTOM_ITEM) { handleCustomColor(lineField, "label.stylepane.custom-line-color", target.getLineColor()); } setTargetLine(); } else if (src == stereoField) { if (target instanceof StereotypeStyled) { Object item = e.getItem(); DefaultComboBoxModel model = (DefaultComboBoxModel) stereoField.getModel(); int idx = model.getIndexOf(item); StereotypeStyled fig = (StereotypeStyled) target; fig.setStereotypeStyle(StereotypeStyle.getEnum(idx)); } } } } /* * @see java.awt.event.FocusListener#focusGained(java.awt.event.FocusEvent) */ public void focusGained(FocusEvent e) { } /** * Makes sure that the fig is updated when the bboxField loses focus. * * {@inheritDoc} */ public void focusLost(FocusEvent e) { if (e.getSource() == bboxField) { setTargetBBox(); } } /* * @see java.awt.event.KeyListener#keyPressed(java.awt.event.KeyEvent) */ public void keyPressed(KeyEvent e) { // Empty implementation - we only care about keyTyped } /* * @see java.awt.event.KeyListener#keyReleased(java.awt.event.KeyEvent) */ public void keyReleased(KeyEvent e) { // Empty implementation - we only care about keyTyped } /* * Tests if enter is pressed in the _bbodField so we need to set the target * bounds. * * @see java.awt.event.KeyListener#keyTyped(java.awt.event.KeyEvent) */ public void keyTyped(KeyEvent e) { if (e.getSource().equals(bboxField) && e.getKeyChar() == '\n') { setTargetBBox(); } } /** * @return Returns the _bboxLabel. */ protected JLabel getBBoxLabel() { return bboxLabel; } /** * @return Returns the _bboxField. */ protected JTextField getBBoxField() { return bboxField; } /** * @return Returns the _fillLabel. */ protected JLabel getFillLabel() { return fillLabel; } /** * @return Returns the _fillField. */ protected JComboBox getFillField() { return fillField; } /** * @return Returns the _lineLabel. */ protected JLabel getLineLabel() { return lineLabel; } /** * @return Returns the _lineField. */ protected JComboBox getLineField() { return lineField; } /** * @return Returns the _spacer. */ protected SpacerPanel getSpacer() { return spacer; } /** * @return Returns the _spacer2. */ protected SpacerPanel getSpacer2() { return spacer2; } /** * @return Returns the _spacer3. */ protected SpacerPanel getSpacer3() { return spacer3; } /** * The UID. */ private static final long serialVersionUID = -6232843473753751128L; /** * @return returns the translated name of the "Custom..." item. */ protected static String getCustomItemName() { return CUSTOM_ITEM; } }