Java tutorial
// -*- mode: java; c-basic-offset: 2; -*- // Copyright 2009-2011 Google, All Rights reserved // Copyright 2011-2012 MIT, All rights reserved // Released under the Apache License, Version 2.0 // http://www.apache.org/licenses/LICENSE-2.0 package com.google.appinventor.client.editor.simple.components; import static com.google.appinventor.client.Ode.MESSAGES; import com.google.appinventor.client.editor.simple.SimpleComponentDatabase; import com.google.appinventor.client.Images; import com.google.appinventor.client.Ode; import com.google.appinventor.client.TranslationDesignerPallete; import com.google.appinventor.client.editor.simple.SimpleEditor; import com.google.appinventor.client.editor.youngandroid.YaBlocksEditor; import com.google.appinventor.client.explorer.SourceStructureExplorerItem; import com.google.appinventor.client.explorer.project.Project; import com.google.appinventor.client.output.OdeLog; import com.google.appinventor.client.widgets.ClonedWidget; import com.google.appinventor.client.widgets.LabeledTextBox; import com.google.appinventor.client.widgets.dnd.DragSource; import com.google.appinventor.client.widgets.dnd.DragSourceSupport; import com.google.appinventor.client.widgets.dnd.DropTarget; import com.google.appinventor.client.widgets.properties.EditableProperties; import com.google.appinventor.client.widgets.properties.EditableProperty; import com.google.appinventor.client.widgets.properties.PropertyChangeListener; import com.google.appinventor.client.widgets.properties.PropertyEditor; import com.google.appinventor.client.widgets.properties.TextPropertyEditor; import com.google.appinventor.client.youngandroid.TextValidators; import com.google.appinventor.shared.rpc.project.HasAssetsFolder; import com.google.appinventor.shared.rpc.project.ProjectNode; import com.google.appinventor.shared.rpc.project.youngandroid.YoungAndroidAssetsFolder; import com.google.appinventor.shared.rpc.project.youngandroid.YoungAndroidProjectNode; import com.google.appinventor.shared.settings.SettingsConstants; import com.google.appinventor.shared.storage.StorageUtil; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.event.dom.client.KeyCodes; import com.google.gwt.event.dom.client.KeyUpEvent; import com.google.gwt.event.dom.client.KeyUpHandler; import com.google.gwt.user.client.Command; import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.DeferredCommand; import com.google.gwt.user.client.Event; import com.google.gwt.user.client.Random; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.DialogBox; import com.google.gwt.user.client.ui.HTML; import com.google.gwt.user.client.ui.HorizontalPanel; import com.google.gwt.user.client.ui.Image; import com.google.gwt.user.client.ui.MouseListener; import com.google.gwt.user.client.ui.MouseListenerCollection; import com.google.gwt.user.client.ui.SourcesMouseEvents; import com.google.gwt.user.client.ui.TreeItem; import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.user.client.ui.Widget; import com.google.gwt.user.client.ui.impl.ClippedImagePrototype; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; /** * Abstract superclass for all components in the visual designer. * * <p>Since the actual component implementation are for a target platform * that is different from the platform used to implement the development * environment, we need to mock them. * * @author lizlooney@google.com (Liz Looney) */ public abstract class MockComponent extends Composite implements PropertyChangeListener, SourcesMouseEvents, DragSource { // Common property names (not all components support all properties). protected static final String PROPERTY_NAME_NAME = "Name"; protected static final String PROPERTY_NAME_UUID = "Uuid"; protected static final String PROPERTY_NAME_SOURCE = "Source"; protected static final List<String> YAIL_NAMES = Arrays.asList("CsvUtil", "Double", "Float", "Integer", "JavaCollection", "JavaIterator", "KawaEnvironment", "Long", "Short", "SimpleForm", "String", "Pattern", "YailList", "YailNumberToString", "YailRuntimeError"); private static final int ICON_IMAGE_WIDTH = 16; private static final int ICON_IMAGE_HEIGHT = 16; public static final int BORDER_SIZE = 2 + 2; // see ode-SimpleMockComponent in Ya.css /** * This class defines the dialog box for renaming a component. */ private class RenameDialog extends DialogBox { // UI elements private final LabeledTextBox newNameTextBox; RenameDialog(String oldName) { super(false, true); setStylePrimaryName("ode-DialogBox"); setText(MESSAGES.renameTitle()); VerticalPanel contentPanel = new VerticalPanel(); LabeledTextBox oldNameTextBox = new LabeledTextBox(MESSAGES.oldNameLabel()); oldNameTextBox.setText(getName()); oldNameTextBox.setEnabled(false); contentPanel.add(oldNameTextBox); newNameTextBox = new LabeledTextBox(MESSAGES.newNameLabel()); newNameTextBox.setText(oldName); newNameTextBox.getTextBox().addKeyUpHandler(new KeyUpHandler() { @Override public void onKeyUp(KeyUpEvent event) { int keyCode = event.getNativeKeyCode(); if (keyCode == KeyCodes.KEY_ENTER) { handleOkClick(); } else if (keyCode == KeyCodes.KEY_ESCAPE) { hide(); } } }); contentPanel.add(newNameTextBox); Button cancelButton = new Button(MESSAGES.cancelButton()); cancelButton.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { hide(); } }); Button okButton = new Button(MESSAGES.okButton()); okButton.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { handleOkClick(); } }); HorizontalPanel buttonPanel = new HorizontalPanel(); buttonPanel.add(cancelButton); buttonPanel.add(okButton); buttonPanel.setSize("100%", "24px"); contentPanel.add(buttonPanel); contentPanel.setSize("320px", "100%"); add(contentPanel); } private void handleOkClick() { String newName = newNameTextBox.getText(); // Remove leading and trailing whitespace // Replace nonempty sequences of internal spaces by underscores newName = newName.trim().replaceAll("[\\s\\xa0]+", "_"); if (newName.equals(getName())) { hide(); } else if (validate(newName)) { hide(); String oldName = getName(); changeProperty(PROPERTY_NAME_NAME, newName); getForm().fireComponentRenamed(MockComponent.this, oldName); } else { newNameTextBox.setFocus(true); newNameTextBox.selectAll(); } } private boolean validate(String newName) { // Check that it meets the formatting requirements. if (!TextValidators.isValidComponentIdentifier(newName)) { Window.alert(MESSAGES.malformedComponentNameError()); return false; } // Check that it's unique. final List<String> names = editor.getComponentNames(); if (names.contains(newName)) { Window.alert(MESSAGES.duplicateComponentNameError()); return false; } // Check that it is a variable name used in the Yail code if (YAIL_NAMES.contains(newName)) { Window.alert(MESSAGES.badComponentNameError()); return false; } //Check that it is not a Component type name, as this is bad for generics SimpleComponentDatabase COMPONENT_DATABASE = SimpleComponentDatabase.getInstance(); if (COMPONENT_DATABASE.isComponent(newName)) { Window.alert(MESSAGES.sameAsComponentTypeNameError()); return false; } return true; } @Override public void show() { super.show(); DeferredCommand.addCommand(new Command() { @Override public void execute() { newNameTextBox.setFocus(true); newNameTextBox.selectAll(); } }); } } // Image bundle protected static final Images images = Ode.getImageBundle(); // Empty component children array so that we don't have to special case and test for null in // case of no children private static final List<MockComponent> NO_CHILDREN = Collections.emptyList(); // Editor of Simple form source file the component belongs to protected final SimpleEditor editor; private final String type; private final Image iconImage; private final SourceStructureExplorerItem sourceStructureExplorerItem; /** * The state of the branch in the components tree corresponding to this component. */ protected boolean expanded; // Properties of the component // Expose these to individual component subclasses, which might need to // check properties fpr UI manipulation. One example is MockHorizontalArrangement protected final EditableProperties properties; private DragSourceSupport dragSourceSupport; // Component container the component belongs to (this will be null for the root component aka the // form) private MockContainer container; private MouseListenerCollection mouseListeners = new MouseListenerCollection(); /** * Creates a new instance of the component. * * @param editor editor of source file the component belongs to */ MockComponent(SimpleEditor editor, String type, Image iconImage) { this.editor = editor; this.type = type; this.iconImage = iconImage; sourceStructureExplorerItem = new SourceStructureExplorerItem() { @Override public void onSelected() { // are we showing the blocks editor? if so, toggle the component drawer if (Ode.getInstance().getCurrentFileEditor() instanceof YaBlocksEditor) { YaBlocksEditor blocksEditor = (YaBlocksEditor) Ode.getInstance().getCurrentFileEditor(); OdeLog.log("Showing item " + getName()); blocksEditor.showComponentBlocks(getName()); } else { select(); } } @Override public void onStateChange(boolean open) { // The user has expanded or collapsed the branch in the components tree corresponding to // this component. Remember that by setting the expanded field so that when we re-build // the tree, we will keep the branch in the same state. expanded = open; } @Override public boolean canRename() { return !isForm(); } @Override public void rename() { if (!isForm()) { new RenameDialog(getName()).center(); } } @Override public boolean canDelete() { return !isForm(); } @Override public void delete() { if (!isForm()) { if (Window.confirm(MESSAGES.reallyDeleteComponent())) { MockComponent.this.delete(); } } } }; expanded = true; // Create a default property set for the component properties = new EditableProperties(true); // Add the mock component itself as a property change listener so that it can update its // visual aspects according to changes of its properties properties.addPropertyChangeListener(this); // Allow dragging this component in a drag-and-drop action if this is not the root form if (!isForm()) { dragSourceSupport = new DragSourceSupport(this); addMouseListener(dragSourceSupport); } } /** * Sets the components widget representation and initializes its properties. * * <p>To be called from implementing constructor. * * @param widget components visual representation in designer */ void initComponent(Widget widget) { // Widget needs to be initialized before the component itself so that the component properties // can be reflected by the widget initWidget(widget); // Capture mouse and click events in onBrowserEvent(Event) sinkEvents(Event.MOUSEEVENTS | Event.ONCLICK); // Add the special name property and set the tooltip String name = componentName(); setTitle(name); addProperty(PROPERTY_NAME_NAME, name, null, new TextPropertyEditor()); // TODO(user): Ensure this value is unique within the project using a list of // already used UUIDs // Set the component's UUID // The default value here can be anything except 0, because YoungAndroidProjectServce // creates forms with an initial Uuid of 0, and Properties.java doesn't encode // default values when it generates JSON for a component. addProperty(PROPERTY_NAME_UUID, "-1", null, new TextPropertyEditor()); changeProperty(PROPERTY_NAME_UUID, "" + Random.nextInt()); editor.getComponentPalettePanel().configureComponent(this); } public boolean isPropertyPersisted(String propertyName) { if (propertyName.equals(PROPERTY_NAME_NAME)) { return false; } return true; } protected boolean isPropertyVisible(String propertyName) { if (propertyName.equals(PROPERTY_NAME_NAME) || propertyName.equals(PROPERTY_NAME_UUID)) { return false; } return true; } protected boolean isPropertyforYail(String propertyName) { // By default we use the same criterion as persistance // This method can then be overriden by the invididual // component Mocks return isPropertyPersisted(propertyName); } /** * Invoked after a component is created from the palette. * * <p>Some subclasses may wish to override this method to initialize * properties of the newly created component. For example, a component with a * caption may want to initialize the caption to match the component's name. */ public void onCreateFromPalette() { } /** * Returns a unique default component name. */ private String componentName() { String compType = TranslationDesignerPallete.getCorrespondingString(getType()); compType = compType.replace(" ", "_").replace("'", "_"); // Make sure it doesn't have any spaces in it return compType + getNextComponentIndex(); } /** * All components have default names for new component instantiations, * usually consisting of the type name and an index. This method * returns the next available component index for this component's type. * * We lower case the typeName and cName so we don't wind up with * components of the names 'fooComponent1' and 'FooComponent1' where * the only difference is the case of the first (or other) * letters. Ultimately the case does matter but when gensyming new * component names components whose only difference is in case will * still result in an incremented index. So if 'fooComponent1' exist * the new component will be 'FooComponent2' instead of * 'FooComponent1'. Hopefully this will be less confusing. * */ private int getNextComponentIndex() { int highIndex = 0; if (editor != null) { final String typeName = TranslationDesignerPallete.getCorrespondingString(getType()).toLowerCase() .replace(" ", "_").replace("'", "_"); final int nameLength = typeName.length(); for (String cName : editor.getComponentNames()) { cName = cName.toLowerCase(); try { if (cName.startsWith(typeName)) { highIndex = Math.max(highIndex, Integer.parseInt(cName.substring(nameLength))); } } catch (NumberFormatException e) { continue; } } } return highIndex + 1; } /** * Adds a new property for the component. * * @param name property name * @param defaultValue default value of property * @param caption property's caption for use in the ui * @param editor property editor */ public final void addProperty(String name, String defaultValue, String caption, PropertyEditor editor) { int type = EditableProperty.TYPE_NORMAL; if (!isPropertyPersisted(name)) { type |= EditableProperty.TYPE_NONPERSISTED; } if (!isPropertyVisible(name)) { type |= EditableProperty.TYPE_INVISIBLE; } if (isPropertyforYail(name)) { type |= EditableProperty.TYPE_DOYAIL; } properties.addProperty(name, defaultValue, caption, editor, type); } /** * Returns the component name. * <p> * This should not be called prior to {@link #initComponent(Widget)}. * * @return component name */ public String getName() { return properties.getPropertyValue(PROPERTY_NAME_NAME); } /** * Returns true if there is a property with the given name. * * @param name property name * @return true if the property exists */ public boolean hasProperty(String name) { return properties.getProperty(name) != null; } /** * Returns the property's value. * * @param name property name * @return property value */ public String getPropertyValue(String name) { return properties.getPropertyValue(name); } /** * Changes the value of a component property. * * @param name property name * @param value new property value */ public void changeProperty(String name, String value) { properties.changePropertyValue(name, value); } /** * Returns the properties set for the component. * * @return properties */ public EditableProperties getProperties() { return properties; } /** * Returns the children of this component. Note that the return value will * never be {@code null} but rather an empty array for components without * children. * <p> * The returned list should not be modified. * * @return children of the component */ public List<MockComponent> getChildren() { return NO_CHILDREN; } /** * Returns the visible children of this component that should be showing. * <p> * The returned list should not be modified. */ public final List<MockComponent> getShowingVisibleChildren() { List<MockComponent> allChildren = getChildren(); if (allChildren.size() == 0) { return NO_CHILDREN; } List<MockComponent> showingVisibleChildren = new ArrayList<MockComponent>(); for (MockComponent child : allChildren) { if (child.isVisibleComponent() && child.showComponentInDesigner()) { showingVisibleChildren.add(child); } } return showingVisibleChildren; } /** * Returns the visible children of this component that should be hidden. * <p> * The returned list should not be modified. */ public final List<MockComponent> getHiddenVisibleChildren() { List<MockComponent> allChildren = getChildren(); if (allChildren.size() == 0) { return NO_CHILDREN; } List<MockComponent> hiddenVisibleChildren = new ArrayList<MockComponent>(); for (MockComponent child : allChildren) { if (child.isVisibleComponent() && !child.showComponentInDesigner()) { hiddenVisibleChildren.add(child); } } return hiddenVisibleChildren; } /** * Returns the form containing this component. * * @return containing form */ public MockForm getForm() { return getContainer().getForm(); } public boolean isForm() { return false; } /** * Indicates whether a component has a visible representation. * <p> * The return value of this method will not change upon successive invocations. * * @return {@code true} if there is a visible representation for the * component, otherwise {@code false} */ public abstract boolean isVisibleComponent(); /** * Selects this component in the visual editor. */ public final void select() { getForm().setSelectedComponent(this); } /** * Invoked when the selection state of this component changes. * <p> * Implementations may override this method to perform additional * alterations to their appearance based on their new selection state. * Overriders must call {@code super.onSelectedChange(selected)} * before performing their own alterations. */ protected void onSelectedChange(boolean selected) { if (selected) { addStyleDependentName("selected"); } else { removeStyleDependentName("selected"); } getForm().fireComponentSelectionChange(this, selected); } /** * Returns whether this component is selected. */ public boolean isSelected() { return (getForm().getSelectedComponent() == this); } /** * Returns the type of the component. * The return value must not change between invocations. * <p> * This is used in the serialization format of the component. * * @return component type */ public final String getType() { return type; } /** * Returns the user-visible type name of the component. * By default this is the internal type string. * * @return component type name */ public String getVisibleTypeName() { return getType(); } /** * Returns the icon's image for the component (e.g. to be used on the component palette). * The return value must not change between invocations. * * @return icon for the component */ public final Image getIconImage() { return iconImage; } /** * Returns the unique id for the component * * @return uuid for the component */ public final String getUuid() { return getPropertyValue(PROPERTY_NAME_UUID); } /** * Sets the component container to which the component belongs. * * @param container owning component container for this component */ protected final void setContainer(MockContainer container) { this.container = container; } /** * Returns the component container to which the component belongs. * * @return owning component container for this component */ protected final MockContainer getContainer() { return container; } /** * Constructs a tree item for the component which will be displayed in the * source structure explorer. * * @return tree item for this component */ protected TreeItem buildTree() { // Instantiate new tree item for this component // Note: We create a ClippedImagePrototype because we need something that can be // used to get HTML for the iconImage. AbstractImagePrototype requires // an ImageResource, which we don't necessarily have. String imageHTML = new ClippedImagePrototype(iconImage.getUrl(), iconImage.getOriginLeft(), iconImage.getOriginTop(), ICON_IMAGE_WIDTH, ICON_IMAGE_HEIGHT).getHTML(); TreeItem itemNode = new TreeItem(new HTML("<span>" + imageHTML + getName() + "</span>")); itemNode.setUserObject(sourceStructureExplorerItem); return itemNode; } /** * If this component isn't a Form, and this component's type isn't already in typesAndIcons, * adds this component's type name as a key to typesAndIcons, mapped to the HTML string used * to display the component type's icon. Subclasses that contain components should override * this to add their own info as well as that for their contained components. * @param typesAndIcons */ public void collectTypesAndIcons(Map<String, String> typesAndIcons) { String name = getVisibleTypeName(); if (!isForm() && !typesAndIcons.containsKey(name)) { String imageHTML = new ClippedImagePrototype(iconImage.getUrl(), iconImage.getOriginLeft(), iconImage.getOriginTop(), ICON_IMAGE_WIDTH, ICON_IMAGE_HEIGHT).getHTML(); typesAndIcons.put(name, imageHTML); } } /** * Returns the source structure explorer item for this component. */ public final SourceStructureExplorerItem getSourceStructureExplorerItem() { return sourceStructureExplorerItem; } /** * Returns the asset node with the given name. * * @param name asset name * @return asset node found or {@code null} */ protected ProjectNode getAssetNode(String name) { Project project = Ode.getInstance().getProjectManager().getProject(editor.getProjectId()); if (project != null) { HasAssetsFolder<YoungAndroidAssetsFolder> hasAssetsFolder = (YoungAndroidProjectNode) project .getRootNode(); for (ProjectNode asset : hasAssetsFolder.getAssetsFolder().getChildren()) { if (asset.getName().equals(name)) { return asset; } } } return null; } /** * Converts the given image property value to an image url. * Returns null if the image property value is blank or not recognized as an * asset. */ protected String convertImagePropertyValueToUrl(String text) { if (text.length() > 0) { ProjectNode asset = getAssetNode(text); if (asset != null) { return StorageUtil.getFileUrl(asset.getProjectId(), asset.getFileId()); } } return null; } // For debugging purposes only private String describeElement(com.google.gwt.dom.client.Element element) { if (element == null) { return "null"; } if (element == getElement()) { return "this"; } try { return element.getTagName(); } catch (com.google.gwt.core.client.JavaScriptException e) { // Can get here if the browser throws a permission denied error return "????"; } } /** * Invoked by GWT whenever a browser event is dispatched to this component. */ @Override public final void onBrowserEvent(Event event) { switch (event.getTypeInt()) { case Event.ONMOUSEDOWN: case Event.ONMOUSEUP: case Event.ONMOUSEMOVE: case Event.ONMOUSEOVER: case Event.ONMOUSEOUT: cancelBrowserEvent(event); mouseListeners.fireMouseEvent(this, event); break; case Event.ONCLICK: cancelBrowserEvent(event); select(); break; default: // Ignore unexpected events break; } } /* * Prevent browser from doing its own event handling and consume event */ private static void cancelBrowserEvent(Event event) { DOM.eventPreventDefault(event); DOM.eventCancelBubble(event, true); } // SourcesMouseEvents implementation /** * Adds the specified mouse-listener to this component's widget. * The listener will be notified of mouse events. */ @Override public final void addMouseListener(MouseListener listener) { mouseListeners.add(listener); } /** * Removes the specified mouse-listener from this component's widget. */ @Override public final void removeMouseListener(MouseListener listener) { mouseListeners.remove(listener); } // DragSource implementation @Override public final void onDragStart() { // no action until createDragWidget() is called } @Override public final Widget createDragWidget(int x, int y) { // TODO(user): Make sure the cloned widget does NOT appear in the // selected state, even if the original widget is in // the selected state. Widget w = new ClonedWidget(this); DragSourceSupport.configureDragWidgetToAppearWithCursorAt(w, x, y); // Hide this element, but keep taking up space in the UI. // This must be done after the drag-widget is created so that // the drag widget itself isn't hidden. setVisible(false); return w; } @Override public Widget getDragWidget() { return dragSourceSupport.getDragWidget(); } @Override public DropTarget[] getDropTargets() { final List<DropTarget> targetsWithinForm = getForm().getDropTargetsWithin(); return targetsWithinForm.toArray(new DropTarget[targetsWithinForm.size()]); } @Override public final void onDragEnd() { // Reshow this element setVisible(true); } /** * Returns the preferred width of the component if there was no layout restriction, * including the CSS border. * <p> * Callers should be aware that most components cannot calculate their * preferred size correctly until they are attached to the UI; see {@link #isAttached()}. * Unattached components are liable to return {@code 0} for any query about their preferred size. * * @return preferred width */ // TODO(user): see getPreferredHeight()! public int getPreferredWidth() { return MockComponentsUtil.getPreferredWidth(this); } /** * Returns the preferred height of the component if there was no layout restriction, * including the CSS border. * <p> * Callers should be aware that most components cannot calculate their * preferred size correctly until they are attached to the UI; see {@link #isAttached()}. * Unattached components are liable to return {@code 0} for any query about their preferred size. * * @return preferred height */ // TODO(user): The concept of preferred height/width is implemented completely wrong. // Currently we are taking the default size of GWT components. This should be // implemented to match the behavior of the Android components being mocked. public int getPreferredHeight() { return MockComponentsUtil.getPreferredHeight(this); } /* * Returns true if this component should be shown in the designer. */ private boolean showComponentInDesigner() { if (hasProperty(MockVisibleComponent.PROPERTY_NAME_VISIBLE)) { boolean visible = Boolean.parseBoolean(getPropertyValue(MockVisibleComponent.PROPERTY_NAME_VISIBLE)); // If this component's visible property is false, we need to check whether to show hidden // components. if (!visible) { boolean showHiddenComponents = Boolean.parseBoolean(editor.getProjectEditor() .getProjectSettingsProperty(SettingsConstants.PROJECT_YOUNG_ANDROID_SETTINGS, SettingsConstants.YOUNG_ANDROID_SETTINGS_SHOW_HIDDEN_COMPONENTS)); return showHiddenComponents; } } return true; } int getWidthHint() { return Integer.parseInt(getPropertyValue(MockVisibleComponent.PROPERTY_NAME_WIDTH)); } int getHeightHint() { return Integer.parseInt(getPropertyValue(MockVisibleComponent.PROPERTY_NAME_HEIGHT)); } /** * Refreshes the form. * * <p>This method should be called whenever a property that affects the size * of the component is changed. */ final void refreshForm() { if (isAttached()) { if (getContainer() != null || isForm()) { getForm().refresh(); } } } // PropertyChangeListener implementation @Override public void onPropertyChange(String propertyName, String newValue) { if (propertyName.equals(PROPERTY_NAME_NAME)) { setTitle(newValue); } else if (getContainer() != null || isForm()) { /* If we've already placed the component onto a Form (and therefore * into a container) then call fireComponentPropertyChanged(). * It's not really an instantiated component until its been added to * a container. If we don't make this test then we end up calling * fireComponentPropertyChanged when we start dragging the component from * the palette. We need to explicitly trigger on Form here, because forms * are not in containers. */ getForm().fireComponentPropertyChanged(this, propertyName, newValue); } } public void onRemoved() { } public void delete() { OdeLog.log("Got delete component for " + this.getName()); this.editor.getProjectEditor().clearLocation(getName()); getForm().select(); // Pass true to indicate that the component is being permanently deleted. getContainer().removeComponent(this, true); // tell the component its been removed, so it can remove children's blocks onRemoved(); properties.removePropertyChangeListener(this); properties.clear(); } // Layout LayoutInfo createLayoutInfo(Map<MockComponent, LayoutInfo> layoutInfoMap) { return new LayoutInfo(layoutInfoMap, this) { @Override int calculateAutomaticWidth() { return getPreferredWidth(); } @Override int calculateAutomaticHeight() { return getPreferredHeight(); } }; } }