com.google.blockly.model.BlocklyCategory.java Source code

Java tutorial

Introduction

Here is the source code for com.google.blockly.model.BlocklyCategory.java

Source

/*
 *  Copyright 2015 Google Inc. All Rights Reserved.
 *  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.google.blockly.model;

import android.support.annotation.IntDef;
import android.support.v4.util.SimpleArrayMap;
import android.text.TextUtils;
import android.util.Log;

import com.google.blockly.android.FlyoutFragment;
import com.google.blockly.utils.BlockLoadingException;
import com.google.blockly.utils.ColorUtils;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;

import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;

/**
 * A category of a toolbox, which holds zero or more blocks or zero or more subcategories. Not both.
 * {@link FlyoutFragment} is responsible for displaying this.
 */
public class BlocklyCategory {
    private static final String TAG = "BlocklyCategory";

    public static final SimpleArrayMap<String, CategoryFactory> CATEGORY_FACTORIES = new SimpleArrayMap<>();

    /** Array used for by {@link ColorUtils#parseColor(String, float[], int)} during I/O. **/
    private static final float[] TEMP_IO_THREAD_FLOAT_ARRAY = new float[3];

    protected final List<BlocklyCategory> mSubcategories = new ArrayList<>();
    protected final List<CategoryItem> mItems = new ArrayList<>();
    // As displayed in the toolbox.
    private String mCategoryName;
    private String mCustomType;
    private Integer mColor = null;
    private boolean mIsVariableCategory = false;
    private boolean mIsFunctionCategory = false;
    private Callback mCallback;

    public void setCallback(Callback callback) {
        mCallback = callback;
    }

    /**
     * @return The user visible name of this category.
     */
    public String getCategoryName() {
        return mCategoryName;
    }

    /**
     * @return The custome type of this category.
     */
    public String getCustomType() {
        return mCustomType;
    }

    /**
     * Convenience method for checking if this category has the custom "VARIABLE" type.
     *
     * @return True if this category has the custom type "VARIABLE"
     */
    public boolean isVariableCategory() {
        return mIsVariableCategory;
    }

    /**
     * Convenience method for checking if this category has the custom "FUNCTION" type.
     *
     * @return True if this category has the custom type "FUNCTION"
     */
    public boolean isFunctionCategory() {
        return mIsFunctionCategory;
    }

    /**
     * Gets the list of blocks in this category. The list should not be modified directly, instead
     * {@link #addItem(CategoryItem)} and {@link #removeItem(CategoryItem)} should be used.
     *
     * @return The list of blocks in this category.
     */
    public List<CategoryItem> getItems() {
        return mItems;
    }

    public List<BlocklyCategory> getSubcategories() {
        return mSubcategories;
    }

    public Integer getColor() {
        return mColor;
    }

    /**
     * Add a {@link Block} to the blocks displayed in this category.
     *
     * @param item The {@link Block} to add.
     */
    public void addItem(CategoryItem item) {
        mItems.add(item);
        if (mCallback != null) {
            mCallback.onItemAdded(mItems.size() - 1, item);
        }
    }

    /**
     * Add a {@link Block} to the blocks displayed in this category at the specified index.
     *
     * @param index The index to insert the block at.
     * @param item The {@link Block} to add.
     */
    public void addItem(int index, CategoryItem item) {
        mItems.add(index, item);
        if (mCallback != null) {
            mCallback.onItemAdded(index, item);
        }
    }

    /**
     * Removes an item from this category.
     *
     * @param item The item to remove.
     * @return true if the item was found and removed, false otherwise.
     */
    public boolean removeItem(CategoryItem item) {
        int i = mItems.indexOf(item);
        if (i != -1) {
            return removeItem(i);
        }
        return false;
    }

    /**
     * Removes an item from this category.
     *
     * @param index The position of the item to remove.
     * @return true if the item was removed, otherwise an OOBE will be thrown.
     */
    public boolean removeItem(int index) {
        CategoryItem item = mItems.remove(index);
        if (mCallback != null) {
            mCallback.onItemRemoved(index, item);
        }
        return true;
    }

    /**
     * Convenience method for removing a {@link BlockItem} from this category by its block.
     *
     * @param block The block to locate and remove.
     * @return true if an item with that block was found and removed, false otherwise.
     */
    public boolean removeBlock(Block block) {
        for (int i = 0; i < mItems.size(); i++) {
            CategoryItem item = mItems.get(i);
            if (item.getType() == CategoryItem.TYPE_BLOCK) {
                Block currBlock = ((BlockItem) item).getBlock();
                if (currBlock == block) {
                    return removeItem(i);
                }
            }
        }
        return false;
    }

    /**
     * Clear the contents of this category and all subcategories; remove subcategories.
     */
    public void clear() {
        for (int i = 0; i < mSubcategories.size(); i++) {
            mSubcategories.get(i).clear();
        }
        mItems.clear();
        mSubcategories.clear();
        if (mCallback != null) {
            mCallback.onCategoryCleared();
        }
    }

    /**
     * @return True if this category contains no blocks or subcategories, false otherwise.
     */
    public boolean isEmpty() {
        return mSubcategories.isEmpty() && mItems.isEmpty();
    }

    /**
     * Fill the given list with of the {@link Block} instances in this category and its
     * subcategories.
     *
     * @param blocks The list to add to, which is not cleared before adding blocks.
     */
    public void getAllBlocksRecursive(List<Block> blocks) {
        for (CategoryItem item : mItems) {
            if (item.getType() == CategoryItem.TYPE_BLOCK) {
                blocks.add(((BlockItem) item).getBlock());
            }
        }
        for (int i = 0; i < mSubcategories.size(); i++) {
            mSubcategories.get(i).getAllBlocksRecursive(blocks);
        }
    }

    /**
     * Read the full definition of the category's contents in from XML.
     *
     * @param parser The {@link XmlPullParser} to read from.
     * @param factory The {@link BlockFactory} to use to generate blocks from their names.
     *
     * @return A new {@link BlocklyCategory} with the contents given by the XML.
     * @throws BlockLoadingException If any error occurs with the input. It may wrap an IOException
     *                               or XmlPullParserException as a root cause.
     */
    public static BlocklyCategory fromXml(XmlPullParser parser, BlockFactory factory) throws BlockLoadingException {
        try {
            BlocklyCategory result;
            String customType = parser.getAttributeValue("", "custom");
            if (CATEGORY_FACTORIES.containsKey(customType)) {
                result = CATEGORY_FACTORIES.get(customType).obtainCategory(customType);
            } else {
                result = new BlocklyCategory();
            }
            result.mCategoryName = parser.getAttributeValue("", "name");
            result.mCustomType = parser.getAttributeValue("", "custom");
            result.mIsVariableCategory = result.mCustomType != null
                    && TextUtils.equals("VARIABLE", result.mCustomType.toUpperCase());
            result.mIsFunctionCategory = result.mCustomType != null
                    && TextUtils.equals("FUNCTION", result.mCustomType.toUpperCase());
            String colourAttr = parser.getAttributeValue("", "colour");
            if (!TextUtils.isEmpty(colourAttr)) {
                try {
                    result.mColor = ColorUtils.parseColor(colourAttr, TEMP_IO_THREAD_FLOAT_ARRAY);
                } catch (ParseException e) {
                    Log.w(TAG, "Invalid toolbox category colour \"" + colourAttr + "\"");
                }
            }
            int eventType = parser.next();
            while (eventType != XmlPullParser.END_DOCUMENT) {
                String tagname = parser.getName();
                switch (eventType) {
                case XmlPullParser.START_TAG:
                    if (parser.getName().equalsIgnoreCase("category")) {
                        result.addSubcategory(BlocklyCategory.fromXml(parser, factory));
                    } else if (parser.getName().equalsIgnoreCase("block")) {
                        result.addItem(new BlockItem(factory.fromXml(parser)));
                    } else if (parser.getName().equalsIgnoreCase("shadow")) {
                        throw new IllegalArgumentException("Shadow blocks may not be top level toolbox blocks.");
                    }
                    break;
                case XmlPullParser.END_TAG:
                    if (tagname.equalsIgnoreCase("category")) {
                        return result;
                    }
                    break;
                default:
                    break;
                }
                eventType = parser.next();
            }
            return result;
        } catch (IOException | XmlPullParserException e) {
            throw new BlockLoadingException(e);
        }
    }

    /**
     * @param subcategory The category to add under this category.
     */
    public void addSubcategory(BlocklyCategory subcategory) {
        mSubcategories.add(subcategory);
    }

    /**
     * Callback class for listening to changes to this category.
     */
    public abstract static class Callback {
        /**
         * Called when an item is added to this category.
         *
         * @param index The index the item was added at.
         * @param item The item that was added.
         */
        public void onItemAdded(int index, CategoryItem item) {
        }

        /**
         * Called when an item is removed from this category.
         *
         * @param index The index the item was previously at.
         * @param item The item that was removed.
         */
        public void onItemRemoved(int index, CategoryItem item) {
        }

        /**
         * Called when the category is cleared, which removes all its subcategories and items.
         */
        public void onCategoryCleared() {
        }
    }

    /**
     * Wraps items that can be displayed as part of a {@link BlocklyCategory}.
     */
    public abstract static class CategoryItem {
        @Retention(RetentionPolicy.SOURCE)
        @IntDef({ TYPE_BLOCK, TYPE_LABEL, TYPE_BUTTON })
        public @interface ItemType {
        }

        public static final int TYPE_BLOCK = 0;
        public static final int TYPE_LABEL = 1;
        public static final int TYPE_BUTTON = 2;

        private final @ItemType int mType;

        public CategoryItem(@ItemType int type) {
            mType = type;
        }

        public @CategoryItem.ItemType int getType() {
            return mType;
        }
    }

    /**
     * Flyout item that contains a stack blocks.
     */
    public static class BlockItem extends CategoryItem {
        private final Block mBlock;

        public BlockItem(Block block) {
            super(TYPE_BLOCK);
            mBlock = block;
        }

        public Block getBlock() {
            return mBlock;
        }
    }

    /**
     * Flyout item representing a clickable button, such as "Add Variable".
     * TODO (#503): Support style and callback spec
     */
    public static class ButtonItem extends CategoryItem {
        private final String mText;
        private final String mAction;

        public ButtonItem(String text, String action) {
            super(TYPE_BUTTON);
            mText = text;
            mAction = action;
        }

        public String getText() {
            return mText;
        }

        public String getAction() {
            return mAction;
        }
    }

    /**
     * Flyout item representing a label between groups of blocks.
     * TODO (#503): Support styling
     */
    public static class LabelItem extends CategoryItem {
        private final String mText;

        public LabelItem(String text) {
            super(TYPE_LABEL);
            mText = text;
        }

        public String getText() {
            return mText;
        }
    }
}