blob: 78e386ce49b1295b6c2063cbfd4369db664c4bbb [file] [log] [blame]
/*
* Copyright 2000-2009 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.uiDesigner.radComponents;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Comparing;
import com.intellij.uiDesigner.*;
import com.intellij.uiDesigner.core.AbstractLayout;
import com.intellij.uiDesigner.designSurface.ComponentDropLocation;
import com.intellij.uiDesigner.lw.*;
import com.intellij.uiDesigner.palette.Palette;
import com.intellij.uiDesigner.propertyInspector.Property;
import com.intellij.uiDesigner.propertyInspector.PropertyEditor;
import com.intellij.uiDesigner.propertyInspector.PropertyRenderer;
import com.intellij.uiDesigner.propertyInspector.editors.string.StringEditor;
import com.intellij.uiDesigner.shared.BorderType;
import com.intellij.uiDesigner.shared.XYLayoutManager;
import com.intellij.uiDesigner.snapShooter.SnapshotContext;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.border.*;
import java.awt.*;
import java.util.ArrayList;
/**
* @author Anton Katilin
* @author Vladimir Kondratyev
*/
public class RadContainer extends RadComponent implements IContainer {
private static final Logger LOG = Logger.getInstance("#com.intellij.uiDesigner.radComponents.RadContainer");
public static class Factory extends RadComponentFactory {
public RadComponent newInstance(ModuleProvider module, Class aClass, String id) {
return new RadContainer(module, aClass, id);
}
public RadComponent newInstance(final Class componentClass, final String id, final Palette palette) {
return new RadContainer(componentClass, id, palette);
}
}
/**
* value: RadComponent[]
*/
@NonNls
public static final String PROP_CHILDREN = "children";
/**
* Children components
*/
private final ArrayList<RadComponent> myComponents;
/**
* Describes border's type.
*/
@NotNull private BorderType myBorderType;
/**
* Border's title. If border doesn't have any title then
* this member is <code>null</code>.
*/
@Nullable private StringDescriptor myBorderTitle;
private int myBorderTitleJustification;
private int myBorderTitlePosition;
private FontDescriptor myBorderTitleFont;
private ColorDescriptor myBorderTitleColor;
private Insets myBorderSize;
private ColorDescriptor myBorderColor;
protected RadLayoutManager myLayoutManager;
private LayoutManager myDelegeeLayout;
public RadContainer(final ModuleProvider module, final String id) {
this(module, JPanel.class, id);
}
public RadContainer(final ModuleProvider module, final Class aClass, final String id) {
super(module, aClass, id);
myComponents = new ArrayList<RadComponent>();
// By default container doesn't have any special border
setBorderType(BorderType.NONE);
myLayoutManager = createInitialLayoutManager();
if (myLayoutManager != null) {
final LayoutManager layoutManager = myLayoutManager.createLayout();
if (layoutManager != null) {
setLayout(layoutManager);
}
}
}
public RadContainer(@NotNull final Class aClass, @NotNull final String id, final Palette palette) {
this(null, aClass, id);
setPalette(palette);
}
@Nullable
protected RadLayoutManager createInitialLayoutManager() {
String defaultLayoutManager = UIFormXmlConstants.LAYOUT_INTELLIJ;
if (getModule() != null) {
final GuiDesignerConfiguration configuration = GuiDesignerConfiguration.getInstance(getProject());
defaultLayoutManager = configuration.DEFAULT_LAYOUT_MANAGER;
}
try {
return LayoutManagerRegistry.createLayoutManager(defaultLayoutManager);
}
catch (Exception e) {
LOG.error(e);
return new RadGridLayoutManager();
}
}
public Property getInplaceProperty(final int x, final int y) {
// 1. We have to check whether user clicked inside border (if any) or not.
// In this case we have return inplace editor for border text
final Insets insets = getDelegee().getInsets(); // border insets
if (
x < insets.left || x > getWidth() - insets.right ||
y < 0 || y > insets.top
) {
return super.getInplaceProperty(x, y);
}
// 2. Now we are sure that user clicked inside title area
return new MyBorderTitleProperty();
}
@Override
@Nullable
public Property getDefaultInplaceProperty() {
return new MyBorderTitleProperty();
}
@Override
@Nullable
public Rectangle getDefaultInplaceEditorBounds() {
return getBorderInPlaceEditorBounds(new MyBorderTitleProperty());
}
public Rectangle getInplaceEditorBounds(final Property property, final int x, final int y) {
if (property instanceof MyBorderTitleProperty) { // If this is our property
return getBorderInPlaceEditorBounds(property);
}
return super.getInplaceEditorBounds(property, x, y);
}
private Rectangle getBorderInPlaceEditorBounds(final Property property) {
final MyBorderTitleProperty _property = (MyBorderTitleProperty)property;
final Insets insets = getDelegee().getInsets();
return new Rectangle(
insets.left,
0,
getWidth() - insets.left - insets.right,
_property.getPreferredSize().height
);
}
public final LayoutManager getLayout() {
if (myDelegeeLayout != null) {
return myDelegeeLayout;
}
return getDelegee().getLayout();
}
public final void setLayout(final LayoutManager layout) {
// some components (for example, JXCollapsiblePanel from SwingX) have asymmetrical getLayout/setLayout - a different
// layout is returned compared to what was passed to setLayout(). to avoid crashes, we store the layout we passed to
// the component.
myDelegeeLayout = layout;
getDelegee().setLayout(layout);
if (layout instanceof AbstractLayout) {
AbstractLayout aLayout = (AbstractLayout)layout;
for (int i = 0; i < getComponentCount(); i++) {
final RadComponent c = getComponent(i);
aLayout.addLayoutComponent(c.getDelegee(), c.getConstraints());
}
}
}
public final boolean isXY() {
return getLayout() instanceof XYLayoutManager;
}
/**
* @param component component to be added.
* @throws java.lang.IllegalArgumentException
* if <code>component</code> is <code>null</code>
* @throws java.lang.IllegalArgumentException
* if <code>component</code> already exist in the
* container
*/
public final void addComponent(@NotNull final RadComponent component, int index) {
if (myComponents.contains(component)) {
//noinspection HardCodedStringLiteral
throw new IllegalArgumentException("component is already added: " + component);
}
final RadComponent[] oldChildren = myComponents.toArray(new RadComponent[myComponents.size()]);
// Remove from old parent
final RadContainer oldParent = component.getParent();
if (oldParent != null) {
oldParent.removeComponent(component);
}
// Attach to new parent
myComponents.add(index, component);
component.setParent(this);
myLayoutManager.addComponentToContainer(this, component, index);
final RadComponent[] newChildren = myComponents.toArray(new RadComponent[myComponents.size()]);
firePropertyChanged(PROP_CHILDREN, oldChildren, newChildren);
}
public final void addComponent(@NotNull final RadComponent component) {
addComponent(component, myComponents.size());
}
/**
* Removes specified <code>component</code> from the container.
* This method also removes component's delegee from the
* container's delegee. Client code is responsible for revalidation
* of invalid Swing hierarchy.
*
* @param component component to be removed.
* @throws java.lang.IllegalArgumentException
* if <code>component</code>
* is <code>null</code>
* @throws java.lang.IllegalArgumentException
* if <code>component</code>
* doesn't exist in the container
*/
public final void removeComponent(@NotNull final RadComponent component) {
if (!myComponents.contains(component)) {
//noinspection HardCodedStringLiteral
throw new IllegalArgumentException("component is not added: " + component);
}
final RadComponent[] oldChildren = myComponents.toArray(new RadComponent[myComponents.size()]);
// Remove child
component.setParent(null);
myComponents.remove(component);
myLayoutManager.removeComponentFromContainer(this, component);
final RadComponent[] newChildren = myComponents.toArray(new RadComponent[myComponents.size()]);
firePropertyChanged(PROP_CHILDREN, oldChildren, newChildren);
}
public final RadComponent getComponent(final int index) {
return myComponents.get(index);
}
public final int getComponentCount() {
return myComponents.size();
}
public int indexOfComponent(IComponent component) {
return myComponents.indexOf(component);
}
/**
* @return new array with all children
*/
public final RadComponent[] getComponents() {
return myComponents.toArray(new RadComponent[myComponents.size()]);
}
@NotNull
public ComponentDropLocation getDropLocation(@Nullable Point location) {
return getLayoutManager().getDropLocation(this, location);
}
public RadComponent findComponentInRect(final int startRow, final int startCol, final int rowSpan, final int colSpan) {
for (int r = startRow; r < startRow + rowSpan; r++) {
for (int c = startCol; c < startCol + colSpan; c++) {
final RadComponent result = getComponentAtGrid(r, c);
if (result != null) {
return result;
}
}
}
return null;
}
@Nullable
public RadComponent getComponentAtGrid(boolean rowFirst, int coord1, int coord2) {
return rowFirst ? getComponentAtGrid(coord1, coord2) : getComponentAtGrid(coord2, coord1);
}
@Nullable
public RadComponent getComponentAtGrid(int row, int col) {
return getGridLayoutManager().getComponentAtGrid(this, row, col);
}
public int getGridRowCount() {
return getGridLayoutManager().getGridRowCount(this);
}
public int getGridColumnCount() {
return getGridLayoutManager().getGridColumnCount(this);
}
public int getGridCellCount(boolean isRow) {
return isRow ? getGridRowCount() : getGridColumnCount();
}
public int getGridRowAt(int y) {
return getGridLayoutManager().getGridRowAt(this, y);
}
public int getGridColumnAt(int x) {
return getGridLayoutManager().getGridColumnAt(this, x);
}
/**
* @return border's type.
* @see com.intellij.uiDesigner.shared.BorderType
*/
@NotNull
public final BorderType getBorderType() {
return myBorderType;
}
/**
* @throws java.lang.IllegalArgumentException
* if <code>type</code>
* is <code>null</code>
* @see com.intellij.uiDesigner.shared.BorderType
*/
public final void setBorderType(@NotNull final BorderType type) {
if (myBorderType == type) {
return;
}
myBorderType = type;
updateBorder();
}
/**
* @return border's title. If the container doesn't have any title then the
* method returns <code>null</code>.
*/
@Nullable
public final StringDescriptor getBorderTitle() {
return myBorderTitle;
}
/**
* @param title new border's title. <code>null</code> means that
* the containr doesn't have have titled border.
*/
public final void setBorderTitle(final StringDescriptor title) {
if (Comparing.equal(title, myBorderTitle)) {
return;
}
myBorderTitle = title;
updateBorder();
}
public int getBorderTitleJustification() {
return myBorderTitleJustification;
}
public void setBorderTitleJustification(final int borderTitleJustification) {
if (myBorderTitleJustification != borderTitleJustification) {
myBorderTitleJustification = borderTitleJustification;
updateBorder();
}
}
public int getBorderTitlePosition() {
return myBorderTitlePosition;
}
public void setBorderTitlePosition(final int borderTitlePosition) {
if (myBorderTitlePosition != borderTitlePosition) {
myBorderTitlePosition = borderTitlePosition;
updateBorder();
}
}
public FontDescriptor getBorderTitleFont() {
return myBorderTitleFont;
}
public void setBorderTitleFont(final FontDescriptor borderTitleFont) {
if (!Comparing.equal(myBorderTitleFont, borderTitleFont)) {
myBorderTitleFont = borderTitleFont;
updateBorder();
}
}
public ColorDescriptor getBorderTitleColor() {
return myBorderTitleColor;
}
public void setBorderTitleColor(final ColorDescriptor borderTitleColor) {
if (!Comparing.equal(myBorderTitleColor, borderTitleColor)) {
myBorderTitleColor = borderTitleColor;
updateBorder();
}
}
public Insets getBorderSize() {
return myBorderSize;
}
public void setBorderSize(final Insets borderSize) {
if (!Comparing.equal(myBorderSize, borderSize)) {
myBorderSize = borderSize;
updateBorder();
}
}
public ColorDescriptor getBorderColor() {
return myBorderColor;
}
public void setBorderColor(final ColorDescriptor borderColor) {
if (!Comparing.equal(myBorderColor, borderColor)) {
myBorderColor = borderColor;
updateBorder();
}
}
/**
* Updates delegee's border
*/
public boolean updateBorder() {
String title = null;
String oldTitle = null;
if (myBorderTitle != null) {
oldTitle = myBorderTitle.getResolvedValue();
myBorderTitle.setResolvedValue(null);
// NOTE: the explicit getValue() check is required for SnapShooter operation
if (myBorderTitle.getValue() != null) {
title = myBorderTitle.getValue();
}
else {
title = StringDescriptorManager.getInstance(getModule()).resolve(this, myBorderTitle);
}
}
Font font = (myBorderTitleFont != null) ? myBorderTitleFont.getResolvedFont(getDelegee().getFont()) : null;
Color titleColor = (myBorderTitleColor != null) ? myBorderTitleColor.getResolvedColor() : null;
Color borderColor = (myBorderColor != null) ? myBorderColor.getResolvedColor() : null;
getDelegee().setBorder(myBorderType.createBorder(title, myBorderTitleJustification, myBorderTitlePosition,
font, titleColor, myBorderSize, borderColor));
return myBorderTitle != null && !Comparing.equal(oldTitle, myBorderTitle.getResolvedValue());
}
public RadLayoutManager getLayoutManager() {
RadContainer parent = this;
while (parent != null) {
if (parent.myLayoutManager != null) {
return parent.myLayoutManager;
}
parent = parent.getParent();
}
return null;
}
public void setLayoutManager(final RadLayoutManager layoutManager) {
myLayoutManager = layoutManager;
setLayout(myLayoutManager.createLayout());
}
public void setLayoutManager(RadLayoutManager layoutManager, LayoutManager layout) {
myLayoutManager = layoutManager;
setLayout(layout);
}
public RadComponent getActionTargetComponent(RadComponent child) {
return child;
}
@Override
public boolean areChildrenExclusive() {
return myLayoutManager.areChildrenExclusive();
}
@Override
public void refresh() {
for (int i = 0; i < getComponentCount(); i++) {
getComponent(i).refresh();
}
myLayoutManager.refresh(this);
}
/**
* Serializes container's border
*/
protected final void writeBorder(final XmlWriter writer) {
writer.startElement(UIFormXmlConstants.ELEMENT_BORDER);
try {
writer.addAttribute(UIFormXmlConstants.ATTRIBUTE_TYPE, getBorderType().getId());
if (getBorderTitle() != null) {
final StringDescriptor descriptor = getBorderTitle();
writer.writeStringDescriptor(descriptor, UIFormXmlConstants.ATTRIBUTE_TITLE,
UIFormXmlConstants.ATTRIBUTE_TITLE_RESOURCE_BUNDLE,
UIFormXmlConstants.ATTRIBUTE_TITLE_KEY);
}
if (myBorderTitleJustification != 0) {
writer.addAttribute(UIFormXmlConstants.ATTRIBUTE_TITLE_JUSTIFICATION, myBorderTitleJustification);
}
if (myBorderTitlePosition != 0) {
writer.addAttribute(UIFormXmlConstants.ATTRIBUTE_TITLE_POSITION, myBorderTitlePosition);
}
if (myBorderTitleFont != null) {
writer.startElement(UIFormXmlConstants.ELEMENT_FONT);
writer.writeFontDescriptor(myBorderTitleFont);
writer.endElement();
}
if (myBorderTitleColor != null) {
writer.startElement(UIFormXmlConstants.ELEMENT_TITLE_COLOR);
writer.writeColorDescriptor(myBorderTitleColor);
writer.endElement();
}
if (myBorderSize != null) {
writer.startElement(UIFormXmlConstants.ELEMENT_SIZE);
writer.writeInsets(myBorderSize);
writer.endElement();
}
if (myBorderColor != null) {
writer.startElement(UIFormXmlConstants.ELEMENT_COLOR);
writer.writeColorDescriptor(myBorderColor);
writer.endElement();
}
}
finally {
writer.endElement(); // border
}
}
/**
* Serializes container's children
*/
protected final void writeChildren(final XmlWriter writer) {
// Children
writer.startElement("children");
try {
writeChildrenImpl(writer);
}
finally {
writer.endElement(); // children
}
}
protected final void writeChildrenImpl(final XmlWriter writer) {
for (int i = 0; i < getComponentCount(); i++) {
getComponent(i).write(writer);
}
}
public void write(final XmlWriter writer) {
if (isXY()) {
writer.startElement("xy");
}
else {
writer.startElement("grid");
}
try {
writeId(writer);
writeClassIfDifferent(writer, JPanel.class.getName());
writeBinding(writer);
if (myLayoutManager != null) {
writer.addAttribute(UIFormXmlConstants.ATTRIBUTE_LAYOUT_MANAGER, myLayoutManager.getName());
}
getLayoutManager().writeLayout(writer, this);
// Constraints and properties
writeConstraints(writer);
writeProperties(writer);
// Border
writeBorder(writer);
// Children
writeChildren(writer);
}
finally {
writer.endElement(); // xy/grid
}
}
public boolean accept(ComponentVisitor visitor) {
if (!super.accept(visitor)) {
return false;
}
for (int i = 0; i < getComponentCount(); i++) {
final IComponent c = getComponent(i);
if (!c.accept(visitor)) {
return false;
}
}
return true;
}
protected void writeNoLayout(final XmlWriter writer, final String defaultClassName) {
writeId(writer);
writeClassIfDifferent(writer, defaultClassName);
writeBinding(writer);
// Constraints and properties
writeConstraints(writer);
writeProperties(writer);
// Margin and border
writeBorder(writer);
writeChildren(writer);
}
@Override
protected void importSnapshotComponent(final SnapshotContext context, final JComponent component) {
getLayoutManager().createSnapshotLayout(context, component, this, component.getLayout());
importSnapshotBorder(component);
for (Component child : component.getComponents()) {
if (child instanceof JComponent) {
RadComponent childComponent = createSnapshotComponent(context, (JComponent)child);
if (childComponent != null) {
getLayoutManager().addSnapshotComponent(component, (JComponent)child, this, childComponent);
}
}
}
}
private void importSnapshotBorder(final JComponent component) {
Border border = component.getBorder();
if (border != null) {
if (border instanceof TitledBorder) {
TitledBorder titledBorder = (TitledBorder)border;
setBorderTitle(StringDescriptor.create(titledBorder.getTitle()));
setBorderTitleJustification(titledBorder.getTitleJustification());
setBorderTitlePosition(titledBorder.getTitlePosition());
final Font titleFont = titledBorder.getTitleFont();
setBorderTitleFont(new FontDescriptor(titleFont.getName(), titleFont.getStyle(), titleFont.getSize()));
setBorderTitleColor(new ColorDescriptor(titledBorder.getTitleColor()));
border = titledBorder.getBorder();
}
if (border instanceof EtchedBorder) {
setBorderType(BorderType.ETCHED);
}
else if (border instanceof BevelBorder) {
BevelBorder bevelBorder = (BevelBorder)border;
setBorderType(bevelBorder.getBevelType() == BevelBorder.RAISED ? BorderType.BEVEL_RAISED : BorderType.BEVEL_LOWERED);
}
else if (border instanceof EmptyBorder) {
EmptyBorder emptyBorder = (EmptyBorder)border;
setBorderType(BorderType.EMPTY);
setBorderSize(emptyBorder.getBorderInsets());
}
else if (border instanceof LineBorder) {
LineBorder lineBorder = (LineBorder)border;
setBorderType(BorderType.LINE);
setBorderColor(new ColorDescriptor(lineBorder.getLineColor()));
}
}
}
public RadAbstractGridLayoutManager getGridLayoutManager() {
if (!(myLayoutManager instanceof RadAbstractGridLayoutManager)) {
throw new RuntimeException("Not a grid container: " + myLayoutManager);
}
return (RadAbstractGridLayoutManager)myLayoutManager;
}
@Nullable
public RadComponent findComponentWithConstraints(final Object constraints) {
for (RadComponent component : getComponents()) {
if (constraints.equals(component.getCustomLayoutConstraints())) {
return component;
}
}
return null;
}
private final class MyBorderTitleProperty extends Property<RadContainer, StringDescriptor> {
private final StringEditor myEditor;
public MyBorderTitleProperty() {
super(null, "Title");
myEditor = new StringEditor(getProject());
}
public Dimension getPreferredSize() {
return myEditor.getPreferredSize();
}
public StringDescriptor getValue(final RadContainer component) {
return myBorderTitle;
}
protected void setValueImpl(final RadContainer container, final StringDescriptor value) throws Exception {
setBorderTitle(value);
}
@NotNull
public PropertyRenderer<StringDescriptor> getRenderer() {
return null;
}
public PropertyEditor<StringDescriptor> getEditor() {
return myEditor;
}
}
}