/*******************************************************************************
* Copyright 2012-present Pixate, Inc.
*
* 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.pixate.freestyle.styling.adapters;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.xml.parsers.DocumentBuilderFactory;
import android.app.ActionBar;
import android.graphics.RectF;
import android.view.View;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CheckedTextView;
import android.widget.CompoundButton;
import android.widget.EditText;
import android.widget.GridView;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.RadioButton;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.ToggleButton;
import com.pixate.freestyle.styling.PXRuleSet;
import com.pixate.freestyle.styling.PXStyleUtils;
import com.pixate.freestyle.styling.media.PXMediaGroup;
import com.pixate.freestyle.styling.stylers.PXStyler;
import com.pixate.freestyle.styling.stylers.PXStylerContext;
import com.pixate.freestyle.styling.stylers.PXStylerContext.IconPosition;
import com.pixate.freestyle.styling.virtualAdapters.PXVirtualActionBarIconAdapter;
import com.pixate.freestyle.styling.virtualAdapters.PXVirtualActionBarLogoAdapter;
import com.pixate.freestyle.styling.virtualAdapters.PXVirtualActionBarOverflowAdapter;
import com.pixate.freestyle.styling.virtualAdapters.PXVirtualActionBarSplitAdapter;
import com.pixate.freestyle.styling.virtualAdapters.PXVirtualActionBarStackedAdapter;
import com.pixate.freestyle.styling.virtualAdapters.PXVirtualActionBarTabIconAdapter;
import com.pixate.freestyle.styling.virtualAdapters.PXVirtualCheckboxIconAdapter;
import com.pixate.freestyle.styling.virtualAdapters.PXVirtualCheckedTextViewIconAdapter;
import com.pixate.freestyle.styling.virtualAdapters.PXVirtualCompoundButtonIconAdapter;
import com.pixate.freestyle.styling.virtualAdapters.PXVirtualDropDownAdapter;
import com.pixate.freestyle.styling.virtualAdapters.PXVirtualIconAdapter;
import com.pixate.freestyle.styling.virtualAdapters.PXVirtualImageViewImageAdapter;
import com.pixate.freestyle.styling.virtualAdapters.PXVirtualListSelectorAdapter;
import com.pixate.freestyle.styling.virtualAdapters.PXVirtualOverscrollListAdapter;
import com.pixate.freestyle.styling.virtualAdapters.PXVirtualToggleAdapter;
import com.pixate.freestyle.styling.virtualStyleables.PXVirtualActionBarIcon;
import com.pixate.freestyle.styling.virtualStyleables.PXVirtualActionBarLogo;
import com.pixate.freestyle.styling.virtualStyleables.PXVirtualActionBarOverflowImage;
import com.pixate.freestyle.styling.virtualStyleables.PXVirtualActionBarSplit;
import com.pixate.freestyle.styling.virtualStyleables.PXVirtualActionBarStacked;
import com.pixate.freestyle.styling.virtualStyleables.PXVirtualActionBarTabIcon;
import com.pixate.freestyle.styling.virtualStyleables.PXVirtualBottomIcon;
import com.pixate.freestyle.styling.virtualStyleables.PXVirtualCheckboxIcon;
import com.pixate.freestyle.styling.virtualStyleables.PXVirtualCheckedTextViewIcon;
import com.pixate.freestyle.styling.virtualStyleables.PXVirtualCompoundButtonIcon;
import com.pixate.freestyle.styling.virtualStyleables.PXVirtualDropdown;
import com.pixate.freestyle.styling.virtualStyleables.PXVirtualImageViewImage;
import com.pixate.freestyle.styling.virtualStyleables.PXVirtualLeftIcon;
import com.pixate.freestyle.styling.virtualStyleables.PXVirtualListOverscroll;
import com.pixate.freestyle.styling.virtualStyleables.PXVirtualListSelector;
import com.pixate.freestyle.styling.virtualStyleables.PXVirtualRightIcon;
import com.pixate.freestyle.styling.virtualStyleables.PXVirtualStyleable;
import com.pixate.freestyle.styling.virtualStyleables.PXVirtualToggle;
import com.pixate.freestyle.styling.virtualStyleables.PXVirtualTopIcon;
import com.pixate.freestyle.util.CollectionUtil;
import com.pixate.freestyle.util.PXLog;
public abstract class PXStyleAdapter {
// A class-name to style-adapter mapping.
private static Map<String, PXStyleAdapter> sRegistry;
private static final PXStyleAdapter NULL_ADAPTER = new PXStyleAdapter() {
@Override
protected List<PXStyler> createStylers() {
// return empty stylers list
return Collections.emptyList();
}
};
private static final String TAG = PXStyleAdapter.class.getSimpleName();
private HashMap<String, PXStyler> stylersByProperty;
private List<PXStyler> stylers;
public PXStyleAdapter() {
}
// Public instance methods
/**
* Batch update with multiple styles. The given lists have to match in size.
*
* @param ruleSets
* @param contexts
* @return true if styling may continue, false if no further styling should
* be done
*/
public boolean updateStyle(List<PXRuleSet> ruleSets, List<PXStylerContext> contexts) {
// Default behavior is to check validity of the arguments
// and exit early if invalid, or call the single-ruleset, single-context
// version of updateStyle if only one ruleSet and context are in the
// lists.
if (!validateStyleUpdateParameters(ruleSets, contexts)) {
return false;
}
return true;
}
/**
* Returns the element name. This name will be matched against the rule-set
* selector string.
*
* @param object
* @return The element name that will be matched against the rule-set
* selector.
* @see PXMediaGroup#getRuleSets(Object) for the matching process.
*/
public String getElementName(Object object) {
// Null by default, subclasses can override;
return null;
}
public String getStyleId(Object object) {
// Null by default, subclasses can override;
return null;
}
public String getStyleClass(Object object) {
// Null by default, subclasses can override;
return null;
}
public String getStyleKey(Object object) {
// Default implementation. Subclasses may override to extend.
return PXStyleUtils.getSelector(object);
}
/**
* Returns the stylers.
*
* @see com.pixate.freestyle.styling.adapters.PXStyleAdapter#getStylers()
* @see #createStylers()
*/
public synchronized List<PXStyler> getStylers() {
if (stylers == null) {
stylers = createStylers();
}
return stylers;
}
/**
* Creates the stylers for this adapter. Subclasses may hook into this
* method in order to add their own stylers when the stylers are created
* (happens ones per adapter).
*
* @return The stylers
* @see #getStylers()
*/
protected abstract List<PXStyler> createStylers();
public RectF getBounds(Object styleable) {
// Empty rect by default, subclasses can override.
return new RectF();
}
public Object getParent(Object styleable) {
// Null by default, subclasses can override.
return null;
}
public String getElementNamespace(Object styleable) {
// Null by default, subclasses can override.
return null;
}
public String getAttributeValue(Object styleable, String attributeName) {
// Null by default, subclasses can override.
return null;
}
public String getAttributeValue(Object styleable, String attributeName, String namespaceUri) {
// Default behavior is to return the value returned by the
// non-namespaced
// method. But subclasses can override.
return getAttributeValue(styleable, attributeName);
}
/**
* Returns a list of direct child elements of the given parent element. The
* list may contain virtual styleables that will be handled when child
* combinations are detected in the CSS (for example #myId > attr).
*
* @param styleable
* @return A list of child elements. Note that the children order is
* maintained in the result.
*/
public List<Object> getElementChildren(Object styleable) {
// Just return the virtual children in this default implementation.
// Subclasses should override to add their concrete children.
return getVirtualChildren(styleable);
}
/**
* Returns a list of virtual children that should be appended to the
* children of an element that is computed by this adapter. Note that these
* virtual children do not effect the child count.
*
* @param styleable
* @return A list of virtual styleables.
* @see PXVirtualStyleable
*/
protected List<Object> getVirtualChildren(Object styleable) {
return Collections.emptyList();
}
/**
* A convenient way to get the previous sibling of the given element.
*
* @param element
* @return The previous sibling; <code>null</code> if none can be located.
*/
public Object getPreviousSibling(Object styleable) {
return getSiblingAt(styleable, -1);
}
/**
* A convenient way to get the next sibling of the given element.
*
* @param element
* @return The next sibling; <code>null</code> if none can be located.
*/
public Object getNextSibling(Object styleable) {
return getSiblingAt(styleable, 1);
}
/**
* Returns a sibling in a given offset of the given element.
*
* @param element
* @param index
* @return
*/
public Object getSiblingAt(Object styleable, int offset) {
// null by default, subclasses can override.
return null;
}
/**
* Returns the index of the given element that represents its location in
* the children list of its parent.
*
* @param element
* @return The index; -1 in case no index can be resolved.
*/
public int getIndexInParent(Object styleable) {
// -1 by default, subclasses can override.
return -1;
}
/**
* Return a list of pseudo-classes that are recognized by given styleable
* instance.
*
* @param styleable
*/
public List<String> getSupportedPseudoClasses(Object styleable) {
// Implemented by subclasses
return null;
}
/**
* Return a list of pseudo-elements that are recognized by this object
*
* @param styleable
*/
public List<String> getSupportedPseudoElements(Object styleable) {
// FIXME - Not supported at the moment. We need to investigate how to
// (and if) we need to implement this for Android.
return null;
}
/**
* Returns the default pseudo class. Null, if none exists for the given
* styleable.
*
* @param styleable
* @return A pseudo class (may be <code>null</code>)
*/
public String getDefaultPseudoClass(Object styleable) {
// Implemented by subclasses
return null;
}
public int getChildCount(Object styleable) {
// default 0, subclasses can override.
return 0;
}
public int getSiblingsCount(Object element) {
// default 0, subclasses can override.
return 0;
}
protected boolean validateStyleUpdateParameters(List<PXRuleSet> ruleSets,
List<PXStylerContext> contexts) {
if (ruleSets == null || contexts == null) {
if (PXLog.isLogging()) {
PXLog.e(TAG, "Rule sets or contexts were null");
}
return false;
}
if (ruleSets.size() != contexts.size()) {
if (PXLog.isLogging()) {
PXLog.e(TAG, "Rule sets or contexts size did not match");
}
return false;
}
return true;
}
/**
* Generates a number of states from a given single state by artificially.
* This is the default implementation, and it doesn't add any state.
* Subclasses should override this implementation to add the unique states
* they require to function properly.
*
* @param stateValue
* @return An array of states array that will be used to replace values in
* the styleable's existing instance.
*/
public int[][] createAdditionalDrawableStates(int initialValue) {
// just wrap in an int array
return new int[][] { new int[] { initialValue } };
}
/**
* Indicates whether the StateListDrawables that are being created with this
* adapter should adjust their bounds by unionizing their internal drawables
* bounds. By default, this method returns <code>false</code> and subclasses
* should overwrite as necessary.
*
* @return <code>true</code> in case the StateListDrawable should hold a
* union of the bounds.
*/
public boolean shouldAdjustDrawableBounds() {
return false;
}
// Statics
public static void registerStyleAdapter(String className, PXStyleAdapter styleAdapter) {
synchronized (PXStyleAdapter.class) {
if (sRegistry == null) {
sRegistry = new HashMap<String, PXStyleAdapter>();
}
sRegistry.put(className, styleAdapter);
}
}
public static PXStyleAdapter getStyleAdapter(Object objectToStyle) {
synchronized (PXStyleAdapter.class) {
if (sRegistry == null) {
initDefaultStyleAdapters();
}
}
if (objectToStyle == null) {
return NULL_ADAPTER;
}
Class<?> cls = objectToStyle instanceof Class<?> ? (Class<?>) objectToStyle : objectToStyle
.getClass();
PXStyleAdapter result = sRegistry.get(cls.getName());
if (result == null) {
Class<?> ancestorClass = cls.getSuperclass();
while (ancestorClass != null) {
result = sRegistry.get(ancestorClass.getName());
if (result != null) {
// Now that we have it, register it.
registerStyleAdapter(cls.getName(), result);
break;
} else {
ancestorClass = ancestorClass.getSuperclass();
}
}
}
if (result == null) {
// Fallback no-op
result = NULL_ADAPTER;
}
return result;
}
public static boolean isStyleable(Object object) {
return getStyleAdapter(object) != NULL_ADAPTER;
}
/**
* Returns true by default to indicate that this adapter supports
* {@link PXStyler}s.
*
* @return <code>true</code> if this adapter supports stylers (default);
* <code>false</code> otherwise.
*/
public boolean isSupportingStylers() {
return true;
}
/**
* Returns a map of property names to stylers that match this adapter. The
* map will be built from the {@link PXStyler} list that is returned from
* the {@link #getStylers()} implementation.
*
* @return A {@link Map} of property names to {@link PXStyler} instances.
*/
public Map<String, PXStyler> getStylersByProperty() {
if (stylersByProperty == null) {
// build a map of property names to stylers
stylersByProperty = new HashMap<String, PXStyler>();
List<PXStyler> viewStylers = getStylers();
if (!CollectionUtil.isEmpty(viewStylers)) {
for (PXStyler styler : viewStylers) {
for (String property : styler.getSupportedProperties()) {
stylersByProperty.put(property, styler);
}
}
}
}
return stylersByProperty;
}
public static void initDefaultStyleAdapters() {
// This all could be in some kind of configuration.
// @formatter:off
registerStyleAdapter(View.class.getName(), PXViewStyleAdapter.getInstance());
registerStyleAdapter(TextView.class.getName(), PXTextViewStyleAdapter.getInstance());
registerStyleAdapter(ListView.class.getName(), PXListViewStyleAdapter.getInstance());
registerStyleAdapter(Button.class.getName(), PXButtonStyleAdapter.getInstance());
registerStyleAdapter(CompoundButton.class.getName(), PXCompoundButtonStyleAdapter.getInstance());
registerStyleAdapter(ToggleButton.class.getName(), PXToggleButtonStyleAdapter.getInstance());
registerStyleAdapter(CheckBox.class.getName(), PXCheckboxStyleAdapter.getInstance());
registerStyleAdapter(RadioButton.class.getName(), PXRadioButtonStyleAdapter.getInstance());
registerStyleAdapter(GridView.class.getName(), PXGridViewStyleAdapter.getInstance());
registerStyleAdapter(ImageView.class.getName(), PXImageViewStyleAdapter.getInstance());
registerStyleAdapter(ImageButton.class.getName(), PXImageButtonStyleAdapter.getInstance());
registerStyleAdapter(Spinner.class.getName(), PXSpinnerStyleAdapter.getInstance());
registerStyleAdapter(EditText.class.getName(), PXEditTextStyleAdapter.getInstance());
registerStyleAdapter(CheckedTextView.class.getName(), PXCheckedTextViewStyleAdapter.getInstance());
registerStyleAdapter("com.android.internal.widget.ScrollingTabContainerView$TabView", PXTabViewStyleAdapter.getInstance());
registerStyleAdapter("com.android.internal.view.menu.ActionMenuPresenter$OverflowMenuButton", PXActionBarOverflowStyleAdapter.getInstance());
// Non-view adapters.
registerStyleAdapter(ActionBar.class.getName(), PXActionBarStyleAdapter.getInstance());
// Virtual adapters.
registerStyleAdapter(PXVirtualListOverscroll.class.getName(), PXVirtualOverscrollListAdapter.getInstance());
registerStyleAdapter(PXVirtualTopIcon.class.getName(), PXVirtualIconAdapter.getInstance(IconPosition.TOP));
registerStyleAdapter(PXVirtualRightIcon.class.getName(), PXVirtualIconAdapter.getInstance(IconPosition.RIGHT));
registerStyleAdapter(PXVirtualBottomIcon.class.getName(), PXVirtualIconAdapter.getInstance(IconPosition.BOTTOM));
registerStyleAdapter(PXVirtualLeftIcon.class.getName(), PXVirtualIconAdapter.getInstance(IconPosition.LEFT));
registerStyleAdapter(PXVirtualCompoundButtonIcon.class.getName(), PXVirtualCompoundButtonIconAdapter.getInstance());
registerStyleAdapter(PXVirtualToggle.class.getName(), PXVirtualToggleAdapter.getInstance());
registerStyleAdapter(PXVirtualCheckboxIcon.class.getName(), PXVirtualCheckboxIconAdapter.getInstance());
registerStyleAdapter(PXVirtualListSelector.class.getName(), PXVirtualListSelectorAdapter.getInstance());
registerStyleAdapter(PXVirtualImageViewImage.class.getName(), PXVirtualImageViewImageAdapter.getInstance());
registerStyleAdapter(PXVirtualDropdown.class.getName(), PXVirtualDropDownAdapter.getInstance());
registerStyleAdapter(PXVirtualCheckedTextViewIcon.class.getName(), PXVirtualCheckedTextViewIconAdapter.getInstance());
registerStyleAdapter(PXVirtualActionBarTabIcon.class.getName(), PXVirtualActionBarTabIconAdapter.getInstance());
registerStyleAdapter(PXVirtualActionBarStacked.class.getName(), PXVirtualActionBarStackedAdapter.getInstance());
registerStyleAdapter(PXVirtualActionBarSplit.class.getName(), PXVirtualActionBarSplitAdapter.getInstance());
registerStyleAdapter(PXVirtualActionBarOverflowImage.class.getName(), PXVirtualActionBarOverflowAdapter.getInstance());
registerStyleAdapter(PXVirtualActionBarIcon.class.getName(), PXVirtualActionBarIconAdapter.getInstance());
registerStyleAdapter(PXVirtualActionBarLogo.class.getName(), PXVirtualActionBarLogoAdapter.getInstance());
// @formatter:on
// This is hideous, but DOM implementations are put on
// devices/emulators.
// At compile time, the DOM is only a set of interfaces, and we need
// concrete classes for our registry.
try {
registerStyleAdapter(DocumentBuilderFactory.newInstance().newDocumentBuilder()
.newDocument().createElement("a").getClass().getName(),
PXDOMStyleAdapter.getInstance());
} catch (Exception e) {
PXLog.e(TAG, e, "Unable to instantiate DOM implementation.");
}
}
}