| /******************************************************************************* |
| * Copyright (c) 2006 Sybase, Inc. and others. |
| * |
| * 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: |
| * Sybase, Inc. - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.jst.pagedesigner.css2.style; |
| |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.Map; |
| |
| import org.eclipse.draw2d.geometry.Insets; |
| import org.eclipse.jst.pagedesigner.IHTMLConstants; |
| import org.eclipse.jst.pagedesigner.converter.ITagConverter; |
| import org.eclipse.jst.pagedesigner.css2.CSSUtil; |
| import org.eclipse.jst.pagedesigner.css2.ICSSStyle; |
| import org.eclipse.jst.pagedesigner.css2.font.CSSFontManager; |
| import org.eclipse.jst.pagedesigner.css2.font.ICSSFont; |
| import org.eclipse.jst.pagedesigner.css2.font.ICSSFontManager; |
| import org.eclipse.jst.pagedesigner.css2.list.CounterHelper; |
| import org.eclipse.jst.pagedesigner.css2.list.CounterValueGenerator; |
| import org.eclipse.jst.pagedesigner.css2.list.ICounterValueGenerator; |
| import org.eclipse.jst.pagedesigner.css2.property.CSSMetaRegistry; |
| import org.eclipse.jst.pagedesigner.css2.property.ICSSPropertyID; |
| import org.eclipse.jst.pagedesigner.css2.property.ICSSPropertyMeta; |
| import org.eclipse.jst.pagedesigner.css2.value.Length; |
| import org.eclipse.jst.pagedesigner.ui.preferences.PDPreferences; |
| import org.eclipse.jst.pagedesigner.utils.DOMUtil; |
| import org.eclipse.wst.sse.core.internal.provisional.INodeNotifier; |
| import org.w3c.dom.Element; |
| import org.w3c.dom.Node; |
| import org.w3c.dom.css.CSSStyleDeclaration; |
| import org.w3c.dom.css.CSSValue; |
| |
| /** |
| * @author mengbo |
| */ |
| public class AbstractStyle implements ICSSStyle |
| { |
| private final Element _element; |
| |
| private Map _cachedValues = new HashMap(); |
| |
| private ICSSFont _font = null; |
| |
| private CSSStyleDeclaration _cache; |
| |
| private CSSStyleDeclaration _defaultCache; |
| |
| private boolean _cssDeclareWasSearched = false; |
| |
| private boolean _cssDefaultDeclareWasSearched = false; |
| |
| private Insets _borderInsets, _marginInsets, _paddingInsets; |
| |
| private ICSSStyle _parentStyle; |
| |
| private HashMap _counters = null; |
| |
| private final PDPreferences _prefs; |
| |
| /** |
| * @return the element this style if for |
| */ |
| public Element getElement() { |
| return _element; |
| } |
| |
| /** |
| * @param element |
| * @param prefs |
| */ |
| public AbstractStyle(Element element, PDPreferences prefs) { |
| _element = element; |
| _prefs = prefs; |
| } |
| |
| /** |
| * reset all the cache. |
| */ |
| public void reset() { |
| _cachedValues.clear(); |
| _font = null; |
| _cache = null; |
| _defaultCache = null; |
| _cssDeclareWasSearched = false; |
| _cssDefaultDeclareWasSearched = false; |
| // if (_counters != null) |
| // { |
| // unregistCounter(); |
| // _counters.clear(); |
| // _counters = null; |
| // } |
| _borderInsets = _marginInsets = _paddingInsets = null; |
| } |
| |
| /** |
| * this is a hook method so caller can use it to override default |
| * calculation. Note, after the call to <code>reset</code>, it will be |
| * lost. |
| * |
| * @param property |
| * @param value |
| */ |
| public void setStyleProperty(String property, Object value) { |
| _cachedValues.put(property, value); |
| } |
| |
| /** |
| * get a style property value. |
| * |
| * @param property |
| * @return the style property |
| */ |
| public Object getStyleProperty(String property) { |
| Object value = _cachedValues.get(property); |
| if (value == null) { |
| value = calculateProperty(property); |
| if (value != null) { |
| _cachedValues.put(property, value); |
| } |
| } |
| return value; |
| } |
| |
| /** |
| * in this method, should first check the "style" attribute, then combine |
| * that with document style. |
| * |
| * @return the style |
| */ |
| protected CSSStyleDeclaration calculateDeclaration() { |
| String name = getHtmlElement().getAttribute("id"); //$NON-NLS-1$ |
| if (name == null || name.length() == 0) { |
| name = getHtmlElement().getAttribute("name"); //$NON-NLS-1$ |
| } |
| return CSSUtil.getCSSDeclaration(this.getHtmlElement(), name); |
| } |
| |
| /** |
| * @return the style |
| */ |
| protected CSSStyleDeclaration calculateDefaultDeclaration() { |
| return CSSUtil.getDefaultCSSDeclaration(this.getHtmlElement(), null); |
| } |
| |
| /** |
| * @return the style declaration |
| */ |
| public CSSStyleDeclaration getDeclaration() { |
| // FIXME:may need to be change, boolean variable is not a best way. |
| if (!_cssDeclareWasSearched) { |
| _cache = calculateDeclaration(); |
| _cssDeclareWasSearched = true; |
| } |
| return _cache; |
| } |
| |
| /** |
| * @return the default declaration |
| */ |
| public CSSStyleDeclaration getDefaultDeclaration() { |
| // FIXME:may need to be change, boolean variable is not a best way. |
| if (!_cssDefaultDeclareWasSearched) { |
| _defaultCache = calculateDefaultDeclaration(); |
| _cssDefaultDeclareWasSearched = true; |
| } |
| return _defaultCache; |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.jst.pagedesigner.css2.ICSSStyle#getHTMLelementInitValue(java.lang.String) |
| */ |
| public Object getHTMLelementInitValue(String propertyName) { |
| ICSSPropertyMeta meta = getPropertyMeta(propertyName); |
| if (meta != null) { |
| Object obj = meta.getHTMLElementInitialValue(_element, |
| getHTMLTag(), propertyName); |
| if (obj == null) { |
| obj = meta.getInitialValue(propertyName, this); |
| } |
| return obj; |
| } |
| return ICSSPropertyMeta.NOT_SPECIFIED; |
| } |
| |
| /** |
| * @param propertyName |
| * @return the property |
| */ |
| protected Object calculateProperty(String propertyName) { |
| ICSSPropertyMeta meta = getPropertyMeta(propertyName); |
| Object result = null; |
| // get declaration |
| CSSStyleDeclaration decl = getDeclaration(); |
| CSSValue value = decl == null ? null : decl |
| .getPropertyCSSValue(propertyName); |
| if (value == null) { |
| if (meta != null) { |
| result = meta.calculateHTMLAttributeOverride(_element, |
| getHTMLTag(), propertyName, this); |
| if (result != null) { |
| return result; |
| } |
| } |
| decl = getDefaultDeclaration(); |
| } |
| value = decl == null ? null : decl.getPropertyCSSValue(propertyName); |
| |
| if (value != null && value.getCssValueType() == CSSValue.CSS_INHERIT) { |
| result = getParentResultValue(meta, propertyName); |
| } else if (value == null) { |
| if (meta != null) { |
| result = meta.calculateHTMLAttributeOverride(_element, |
| getHTMLTag(), propertyName, this); |
| } |
| if (result == null) { |
| result = calculateLocalOverride(meta, propertyName); |
| } |
| if (result == null) { |
| if (meta == null) { |
| result = ICSSPropertyMeta.NOT_SPECIFIED; |
| } else { |
| if (meta.isInherited()) { |
| result = getParentResultValue(meta, propertyName); |
| } else { |
| result = meta.getInitialValue(propertyName, this); |
| } |
| } |
| } |
| } else { |
| result = calculateCSSValueResult(meta, value, propertyName); |
| } |
| return result; |
| } |
| |
| /** |
| * get the corresponding HTML tag for this style. This is for certain HTML |
| * tag can also provide style information. |
| * |
| * @return the html tag |
| */ |
| protected String getHTMLTag() { |
| return _element.getTagName(); |
| } |
| |
| /** |
| * @param propertyName |
| * @return the property meta for property name |
| */ |
| protected ICSSPropertyMeta getPropertyMeta(String propertyName) { |
| return CSSMetaRegistry.getInstance().getMeta(propertyName); |
| } |
| |
| /** |
| * convert the CSSValue to the property type specified data result. |
| * |
| * @param meta |
| * @param value |
| * @param propertyName |
| * @return should not return null. |
| */ |
| protected Object calculateCSSValueResult(ICSSPropertyMeta meta, |
| CSSValue value, String propertyName) { |
| if (meta == null) { |
| return ICSSPropertyMeta.NOT_SPECIFIED; |
| } |
| return meta.calculateCSSValueResult(value, propertyName, this); |
| } |
| |
| /** |
| * it is possible that some attribute of the element may provide style |
| * information. child class should override this method. Also, some element |
| * type may have internally build style, such as input-submit may use |
| * special border. NOTE: it is very important that in calculateLocalOverride |
| * you don't directly or indirectly call getStyleProperty() to avoid |
| * deadloop. |
| * |
| * @param meta |
| * @param propertyName |
| * @return null means no style information in other attributes. Otherwise |
| * return property specific data result -- normally will use meta to |
| * convert the attribute. |
| */ |
| protected Object calculateLocalOverride(ICSSPropertyMeta meta, |
| String propertyName) { |
| // // do some default margin thing. |
| // if (ICSSPropertyID.ATTR_MARGIN_RIGHT.equalsIgnoreCase(propertyName) |
| // || ICSSPropertyID.ATTR_MARGIN_BOTTOM.equalsIgnoreCase(propertyName)) |
| // { |
| // return MARGIN_LENGTH; |
| // } |
| // else if |
| // (ICSSPropertyID.ATTR_MARGIN_LEFT.equalsIgnoreCase(propertyName)) |
| // { |
| // // to make a little room, so it is possible for user to position the |
| // // mouse before the first element in a block. |
| // return MARGIN_LEFT; |
| // } |
| return null; |
| } |
| |
| /** |
| * This is only called when inherit value from parent. |
| * |
| * @param meta |
| * @param propertyName |
| * @return the result value |
| */ |
| protected Object getParentResultValue(ICSSPropertyMeta meta, |
| String propertyName) { |
| ICSSStyle style = getParentStyle(); |
| return style.getStyleProperty(propertyName); |
| } |
| |
| /** |
| * @param parentStyle |
| */ |
| public void setParentStyle(ICSSStyle parentStyle) { |
| this._parentStyle = parentStyle; |
| reset(); |
| } |
| |
| public ICSSStyle getParentStyle() { |
| if (_parentStyle != null) { |
| return _parentStyle; |
| } |
| Node node = _element.getParentNode(); |
| while (node instanceof Element && node instanceof INodeNotifier) { |
| ICSSStyle parentStyle = (ICSSStyle) ((INodeNotifier) node) |
| .getAdapterFor(ICSSStyle.class); |
| if (parentStyle != null) { |
| return parentStyle; |
| } |
| node = node.getParentNode(); |
| } |
| return DefaultStyle.getInstance(); |
| } |
| |
| /** |
| * Will not return null |
| * |
| * @return the font |
| */ |
| public ICSSFont getCSSFont() { |
| if (_font == null) { |
| _font = getFontManager().createFont(this); |
| } |
| return _font; |
| } |
| |
| /** |
| * @return |
| */ |
| private ICSSFontManager getFontManager() { |
| return CSSFontManager.getInstance(); |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.eclipse.jst.pagedesigner.css2.ICSSStyle#dispose() |
| */ |
| public void dispose() { |
| // TODO: anything to dispose? |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.eclipse.jst.pagedesigner.css2.ICSSStyle#getMarginInsets() |
| */ |
| public Insets getMarginInsets() { |
| if (_marginInsets == null) { |
| int top = getInsetProperty(ICSSPropertyID.ATTR_MARGIN_TOP); |
| int left = getInsetProperty(ICSSPropertyID.ATTR_MARGIN_LEFT); |
| int bottom = getInsetProperty(ICSSPropertyID.ATTR_MARGIN_BOTTOM); |
| int right = getInsetProperty(ICSSPropertyID.ATTR_MARGIN_RIGHT); |
| _marginInsets = new Insets(top, left, bottom, right); |
| } |
| return _marginInsets; |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.eclipse.jst.pagedesigner.css2.ICSSStyle#getPaddingInsets() |
| */ |
| public Insets getPaddingInsets() { |
| if (_paddingInsets == null) { |
| int top = getInsetProperty(ICSSPropertyID.ATTR_PADDING_TOP); |
| int left = getInsetProperty(ICSSPropertyID.ATTR_PADDING_LEFT); |
| int bottom = getInsetProperty(ICSSPropertyID.ATTR_PADDING_BOTTOM); |
| int right = getInsetProperty(ICSSPropertyID.ATTR_PADDING_RIGHT); |
| //add extra padding only for the top element of a source tag's rendering |
| if (elementIsTagConverted()) { |
| final int borderOffset = _prefs.getCssArtificialCellPadding(); |
| top += borderOffset; |
| left += borderOffset; |
| bottom += borderOffset; |
| right += borderOffset; |
| } |
| _paddingInsets = new Insets(top, left, bottom, right); |
| } |
| return _paddingInsets; |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.eclipse.jst.pagedesigner.css2.ICSSStyle#getBorderInsets() |
| */ |
| public Insets getBorderInsets() { |
| if (_borderInsets == null) { |
| int top = getInsetProperty(ICSSPropertyID.ATTR_BORDER_TOP_WIDTH); |
| int left = getInsetProperty(ICSSPropertyID.ATTR_BORDER_LEFT_WIDTH); |
| int bottom = getInsetProperty(ICSSPropertyID.ATTR_BORDER_BOTTOM_WIDTH); |
| int right = getInsetProperty(ICSSPropertyID.ATTR_BORDER_RIGHT_WIDTH); |
| _borderInsets = new Insets(top, left, bottom, right); |
| } |
| return _borderInsets; |
| } |
| |
| /** |
| * @param border_top_width |
| * @return |
| */ |
| private int getInsetProperty(String propertyName) { |
| Object obj = this.getStyleProperty(propertyName); |
| if (obj instanceof Length) { |
| Length l = (Length) obj; |
| if (l.isPercentage()) { |
| return 0; // FIXME: |
| } |
| return l.getValue(); |
| } |
| |
| return 0; |
| } |
| |
| public boolean isAdapterForType(Object type) { |
| return (type == ICSSStyle.class); |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.eclipse.jst.pagedesigner.css2.ICSSStyle#isSizeIncludeBorderPadding() |
| */ |
| public boolean isSizeIncludeBorderPadding() { |
| String display = this.getDisplay(); |
| if ("table-cell".equalsIgnoreCase(display)) { //$NON-NLS-1$ |
| return false; |
| } |
| String tag = this.getHTMLTag(); |
| if ("img".equalsIgnoreCase(tag)) { //$NON-NLS-1$ |
| return false; |
| } |
| return true; |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see com.ibm.sse.model.INodeAdapter#notifyChanged(com.ibm.sse.model.INodeNotifier, |
| * int, java.lang.Object, java.lang.Object, java.lang.Object, int) |
| */ |
| public void notifyChanged(INodeNotifier notifier, int eventType, |
| Object changedFeature, Object oldValue, Object newValue, int pos) { |
| // do nothing |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.eclipse.jst.pagedesigner.css2.ICSSStyle#getBackgroundColor() |
| */ |
| public Object getColor() { |
| Object _color = null; |
| if (_color == null) { |
| _color = getStyleProperty(ICSSPropertyID.ATTR_COLOR); |
| if (_color == null) { |
| _color = getStyleProperty(ICSSPropertyID.ATTR_TEXTCOLOR); |
| } |
| } |
| return _color; |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.eclipse.jst.pagedesigner.css2.ICSSStyle#getColor() |
| */ |
| public Object getBackgroundColor() { |
| Object _backgroundColor = null; |
| if (_backgroundColor == null) { |
| _backgroundColor = getStyleProperty(ICSSPropertyID.ATTR_BACKGROUND_COLOR); |
| } |
| return _backgroundColor; |
| } |
| |
| /** |
| * @return the html element |
| */ |
| public Element getHtmlElement() { |
| // if (_element instanceof IDOMElement) |
| // { |
| // EditPart part = (EditPart) ((IDOMElement) |
| // _element).getAdapterFor(EditPart.class); |
| // if (part instanceof ElementEditPart) |
| // { |
| // ElementEditPart elementPart = (ElementEditPart) part; |
| // ITagHandler h = elementPart.getTagHandler(); |
| // if (h != null) |
| // { |
| // return h.mapCustomElement(_element); |
| // } |
| // } |
| // |
| // } |
| return _element; |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.eclipse.jst.pagedesigner.css2.ICSSStyle#getDisplay() |
| */ |
| public String getDisplay() { |
| Object display = this.getStyleProperty(ICSSPropertyID.ATTR_DISPLAY); |
| String displayStr; |
| if (display == null) { |
| displayStr = ICSSPropertyID.VAL_INLINE; |
| } else if (display instanceof String) { |
| displayStr = (String) display; |
| } else { |
| displayStr = display.toString(); |
| } |
| if (ICSSPropertyID.VAL_INLINE.equalsIgnoreCase(displayStr) |
| && IHTMLConstants.TAG_TABLE.equalsIgnoreCase(getHTMLTag())) { |
| return ICSSPropertyID.VAL_INLINE_TABLE; |
| } |
| if (ICSSPropertyID.VAL_INLINE.equalsIgnoreCase(displayStr)) { |
| Object width = this.getStyleProperty(ICSSPropertyID.ATTR_WIDTH); |
| if (width instanceof Length) { |
| return ICSSPropertyID.VAL_INLINE_BLOCK; |
| } |
| Object height = this.getStyleProperty(ICSSPropertyID.ATTR_HEIGHT); |
| if (height instanceof Length) { |
| return ICSSPropertyID.VAL_INLINE_BLOCK; |
| } |
| return displayStr; |
| } |
| return displayStr; |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.eclipse.jst.pagedesigner.css2.ICSSStyle#getListStyle() |
| */ |
| public Map getCounters() { |
| if (_counters == null) { |
| _counters = new HashMap(); |
| CounterHelper.processCounterReset(this, _counters); |
| } |
| return _counters; |
| } |
| |
| /** |
| * Get named counter from counters. |
| * |
| * see org.eclipse.jst.pagedesigner.css2.ICSSStyle#getCounter(java.lang.String) |
| */ |
| public ICounterValueGenerator findCounter(String name, boolean must) { |
| Map counters = getCounters(); |
| if (counters == null || !counters.containsKey(name)) { |
| if (getParentStyle() != null |
| && !(getParentStyle() instanceof DefaultStyle)) { |
| // ensure it is registered somewhere. |
| return getParentStyle().findCounter(name, must); |
| } |
| // must is called by counter-increment |
| else if (must) { |
| // the caller should do the other setting. |
| ICounterValueGenerator counter = new CounterValueGenerator( |
| name, null, null, this); |
| counter.resetCount(); |
| counters.put(name, counter); |
| } |
| } |
| return (ICounterValueGenerator) counters.get(name); |
| } |
| |
| /** |
| * @param buffer |
| */ |
| public void dumpDebugInfo(StringBuffer buffer) { |
| if (_cache != null) { |
| buffer.append("cache:\n"); //$NON-NLS-1$ |
| buffer.append(_cache.getCssText()).append("\n"); //$NON-NLS-1$ |
| } |
| if (_defaultCache != null) { |
| buffer.append("default cache:\n"); //$NON-NLS-1$ |
| buffer.append(_defaultCache.getCssText()).append("\n"); //$NON-NLS-1$ |
| } |
| |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.eclipse.jst.pagedesigner.css2.ICSSStyle#getColSpan() |
| */ |
| public int getColSpan() { |
| int colspan = DOMUtil.getIntAttributeIgnoreCase(getHtmlElement(), |
| "colspan", 1); //$NON-NLS-1$ |
| // if span == 0, means span all col from the current column to end |
| // colume |
| if (colspan < 0) { |
| return 1; |
| } |
| return colspan; |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.eclipse.jst.pagedesigner.css2.ICSSStyle#getRowSpan() |
| */ |
| public int getRowSpan() { |
| int rowspan = DOMUtil.getIntAttributeIgnoreCase(getHtmlElement(), |
| "rowspan", 1); //$NON-NLS-1$ |
| if (rowspan < 0) { |
| return 1; |
| } |
| return rowspan; |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.eclipse.jst.pagedesigner.css2.ICSSStyle#isInSelection() |
| */ |
| public boolean isInSelection() { |
| IRangeSelectionProxy proxy = (IRangeSelectionProxy) getAdapter(IRangeSelectionProxy.class); |
| if (proxy != null) { |
| return proxy.isRangeSelected(); |
| } |
| ICSSStyle parentStyle = getParentStyle(); |
| if (parentStyle != null) { |
| return parentStyle.isInSelection(); |
| } |
| return false; |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.eclipse.core.runtime.IAdaptable#getAdapter(java.lang.Class) |
| */ |
| public Object getAdapter(Class adapter) { |
| if (this._element instanceof INodeNotifier) { |
| Object ret = ((INodeNotifier) _element).getAdapterFor(adapter); |
| if (ret != null && adapter.isInstance(ret)) { |
| return ret; |
| } |
| } |
| return null; |
| } |
| |
| // private void unregistCounter() |
| // { |
| // if (_counters != null) |
| // { |
| // Collection c = _counters.values(); |
| // Iterator iter = c.iterator(); |
| // while (iter.hasNext()) |
| // { |
| // Counter2 counter = (Counter2) iter.next(); |
| // counter.unregist(this); |
| // } |
| // } |
| // } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.eclipse.jst.pagedesigner.css2.ICSSStyle#resetCounters() |
| */ |
| public void processCounters() { |
| this._counters = null; |
| CounterHelper.processCounterIncrement(this); |
| } |
| |
| private boolean elementIsTagConverted() { |
| boolean isTagConverted = false; |
| if (_element instanceof INodeNotifier) { |
| Collection nodeAdapters = ((INodeNotifier)_element).getAdapters(); |
| for (Object nodeAdapter: nodeAdapters) { |
| if (nodeAdapter instanceof ITagConverter) { |
| isTagConverted = true; |
| break; |
| } |
| } |
| } |
| return isTagConverted; |
| } |
| |
| } |