Package edu.mit.blocks.workspace

Source Code of edu.mit.blocks.workspace.Workspace

package edu.mit.blocks.workspace;

import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.swing.JComponent;
import javax.swing.JLayeredPane;
import javax.swing.JSplitPane;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import edu.mit.blocks.codeblocks.Block;
import edu.mit.blocks.codeblockutil.Explorer;
import edu.mit.blocks.codeblockutil.ExplorerEvent;
import edu.mit.blocks.codeblockutil.ExplorerListener;
import edu.mit.blocks.renderable.BlockUtilities;
import edu.mit.blocks.renderable.RenderableBlock;
import edu.mit.blocks.workspace.typeblocking.FocusTraversalManager;
import edu.mit.blocks.workspace.typeblocking.TypeBlockManager;

/**
* The Workspace is the main block area, where blocks are manipulated and assembled.
* This class governs the blocks, the world, the view, drawing, dragging, animating.
*/
public class Workspace extends JLayeredPane implements ISupportMemento, RBParent, ChangeListener, ExplorerListener {

    private static final long serialVersionUID = 328149080422L;

    // the environment wrapps all the components of a workspace (Blocks, RenderableBlocks, BlockStubs, BlockGenus)
    private final WorkspaceEnvironment env = new WorkspaceEnvironment();

    public WorkspaceEnvironment getEnv() {
        return this.env;
    }

    /** WorkspaceListeners that monitor:
     * block: added, removed, dropped, label changed, connected, disconnected
     * workspace: scrolled, zoom changed
     */
    private HashSet<WorkspaceListener> workspaceListeners = new HashSet<WorkspaceListener>();

    /** WorkspaceWidgets are components within the workspace other than blocks that
     * include bars, buttons, factory drawers, and single instance widgets such as
     * the MiniMap and the TrashCan.
     */
    private TreeSet<WorkspaceWidget> workspaceWidgets = new TreeSet<WorkspaceWidget>(
            // store these in a sorted set according to their "draw depth"
            new Comparator<WorkspaceWidget>() {
        @Override
        public int compare(WorkspaceWidget w1, WorkspaceWidget w2) {
            // by returning the difference in "draw depth", we make this comparitor
            // sort according to ascending "draw depth" (i.e. front to back)
            double depth1 = getDrawDepth(w1.getJComponent());
            double depth2 = getDrawDepth(w2.getJComponent());

            if (depth1 > depth2) {
                return 1;
            } else if (depth1 < depth2) {
                return -1;
            }
            //TODO ria should NEVER return zero unless (w1 == w2) otherwise widget will not be added!
            //ask daniel about this
            if (w1 != w2) {
                return -1;
            } else {
                //System.err.println("returned 0: this widget will not be added to workspace widgets: "+w1+ "comparing with: "+w2);
                return 0;
            }
        }
    });

    public static boolean everyPageHasDrawer = false;

    /** The Workspace has a BlockCanvas widget on which blocks actually live.
     * The blockCanvas is what takes care of allowing scrolling and drawing pages,
     * so it is controlled by the Workspace, but it is also a regular WorkspaceWidget
     * for the purposes of drag and drop.
     */
    private BlockCanvas blockCanvas = new BlockCanvas(this);

    /** blockCanvasLayer allows for static components to be laid out beside the block canvas.  One example of
     * such a component would be a static block factory.  In user testing, we found that novice users performed
     * better with a static block factory than one in which they could drag around and toggle the visibility
     * of. */
    private JSplitPane blockCanvasLayer;

    /**
     * MiniMap associated with the blockCanvas
     */
    private MiniMap miniMap;
    private FactoryManager factory;
    private final FocusTraversalManager focusManager;

    private final TypeBlockManager typeBlockManager;

    /// RENDERING LAYERS ///
    public final static Integer PAGE_LAYER = new Integer(0);
    public final static Integer BLOCK_HIGHLIGHT_LAYER = new Integer(1);
    public final static Integer BLOCK_LAYER = new Integer(2);
    public final static Integer WIDGET_LAYER = new Integer(3);
    public final static Integer DRAGGED_BLOCK_HIGHLIGHT_LAYER = new Integer(4);
    public final static Integer DRAGGED_BLOCK_LAYER = new Integer(5);

    public Workspace() {
        super();
        setLayout(null);
        setBackground(Color.WHITE);
        setPreferredSize(new Dimension(1000, 600));

        this.factory = new FactoryManager(this);
        this.addWorkspaceListener(this.factory);
        this.blockCanvas.getHorizontalModel().addChangeListener(this);
        for (final Explorer exp : factory.getNavigator().getExplorers()) {
            exp.addListener(this);
        }

        this.miniMap = new MiniMap(this);
        this.addWidget(this.miniMap, true, true);
        this.addComponentListener(new ComponentAdapter() {
            public void componentResized(ComponentEvent e) {
                miniMap.repositionMiniMap();
                blockCanvas.reformBlockCanvas();
                blockCanvasLayer.setSize(getSize());
                blockCanvasLayer.validate();
            }
        });

        blockCanvasLayer = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true,
                factory.getJComponent(), blockCanvas.getJComponent());
        blockCanvasLayer.setOneTouchExpandable(true);
        blockCanvasLayer.setDividerSize(6);
        add(blockCanvasLayer, BLOCK_LAYER);
        validate();
        addPageAt(Page.getBlankPage(this), 0, false);

        this.workspaceWidgets.add(factory);

        this.focusManager = new FocusTraversalManager(this);

        this.typeBlockManager = new TypeBlockManager(this, blockCanvas);
    }

    /*
     * Implements explorerEventOccurred method in ExplorerListener interface
     * If event is of type "1" then hides the Minimize page button
     * If event is of type "2" then shows the Minimize page button
     * Event type details can be found in the GlassExplorerEvent class
     */
    @Override
    public void explorerEventOccurred(ExplorerEvent event) {
        final Explorer exp = event.getSource();
        if (event.getEventType() == 1) {
            for (final Page p : blockCanvas.getLeftmostPages(exp.getSelectedCanvasWidth())) {
                p.disableMinimize();
            }
        } else if (event.getEventType() == 2) {
            for (final Page p : blockCanvas.getLeftmostPages(exp.getSelectedCanvasWidth())) {
                p.enableMinimize();
            }
        }
    }

    public Dimension getCanvasSize() {
        return blockCanvas.getCanvas().getSize();
    }

    public Dimension getCanvasOffset() {
        return new Dimension(blockCanvas.getHorizontalModel().getValue() - blockCanvas.getJComponent().getX(),
                blockCanvas.getVerticalModel().getValue() - blockCanvas.getJComponent().getY());
    }

    public Page getPageNamed(String pageName) {
        return blockCanvas.getPageNamed(pageName);
    }

    public BlockCanvas getBlockCanvas() {
        return blockCanvas;
    }

    /**
     * @return MiniMap associated with this.blockcanvas
     */
    public MiniMap getMiniMap() {
        return this.miniMap;
    }

    /**
     * Returns the FocusTraversalManager instance
     * @return FocusTraversalManager instance
     */
    public FocusTraversalManager getFocusManager() {
        return focusManager;
    }

    /**
     * Disables the MiniMap from canvas
     *
     */
    public void disableMiniMap() {
        miniMap.hideMiniMap();
    }
    ////////////////
    // WIDGETS
    ////////////////
    private Point p = new Point(0, 0); // this is for speed - faster not to re-create Points

    /**
     * Returns the WorkspaceWidget currently at the specified point
     * @param point the <code>Point2D</code> to get the widget at, given
     *   in Workspace (i.e. window) coordinates
     * @return the WorkspaceWidget currently at the specified point
     */
    public WorkspaceWidget getWidgetAt(Point point) {
        Iterator<WorkspaceWidget> it = workspaceWidgets.iterator();
        //TODO: HUGE HACK, get rid of this. bascally, the facotry has priority
        if (factory.contains(
                SwingUtilities.convertPoint((JComponent) this, point, factory.getJComponent()).x,
                SwingUtilities.convertPoint((JComponent) this, point, factory.getJComponent()).y)) {
            return factory;
        }
        WorkspaceWidget widget = null;
        while (it.hasNext()) {
            //convert point to the widgets' coordinate system
            widget = it.next();
            p = SwingUtilities.convertPoint((JComponent) this, point, widget.getJComponent());
            //test if widget contains point and widget is visible
            if (widget.contains(p.x, p.y) && widget.getJComponent().isVisible()) {
                return widget; // because these are sorted by draw depth, the first hit is on top
            }
        }

        return null; // hopefully we never get here
    }

    /**
     * This helper method retuns a fractional "depth" representing the overall
     * z-order of a component in the workspace.  For example, a component with
     * a "drawDepth" of 1.9 is most likely the first child (rendered on top of,
     * remember) a component with z-order 2 in this container.  1.99 means the
     * first child of the first child, and so on.
     * @param c - the Component whose draw depth is required.  MUST be an eventual child of the Workspace.
     * @return the fractional "drawDepth" of the Component c.
     */
    private double getDrawDepth(Component c) {
        int treeLevel = 0;
        double depth = 0;
        Container p = c.getParent();
        //System.out.println("getting drawer depth for: "+c);
        if (p != null) { //ria added this condition (when removing widgets while resetting, they must have a parent, but by then they have none.
            // figure out how far down the tree this component is
            while (p != this && p != null) {
                p = p.getParent();
                treeLevel++;
            }
            //System.out.println("tree level for "+c+": "+treeLevel);
            // now walk up the tree, assigning small fractions for distant children,
            // and getting more important closer to the top level.
            p = c.getParent();
            for (int level = treeLevel; level >= 0; level--) {
                if (p == null) {
                    break;
                }
                if (level > 0) {
                    depth -= p.getComponentZOrder(c) / Math.pow(10, level);
                } else {
                    depth += p.getComponentZOrder(c);
                }
                c = p;
                p = p.getParent();
            }

            //System.out.println("returned depth "+depth);
            return depth;
        }
        return Double.MAX_VALUE;
    }

    /**
     * Adds the specified widget to this Workspace
     * @param widget the desired widget to add
     * @param floatOverCanvas if true, the Workspace will add and render this widget such that it "floats"
     * above the canvas and its set of blocks.  If false, the widget will be laid out beside the canvas.  This feature
     * only applies if the specified widget is added graphically to the workspace (addGraphically = true)
     * @param addGraphically  a Swing dependent parameter to tell the Workspace whether or not to add
     * the specified widget as a child component.  This parameter should be false for widgets that have a
     * parent already specified
     */
    public void addWidget(WorkspaceWidget widget, boolean addGraphically, boolean floatOverCanvas) {
        if (addGraphically) {
            if (floatOverCanvas) {
                this.add((JComponent) widget, WIDGET_LAYER);
                widget.getJComponent().setVisible(true);
                revalidate();
                repaint();
            } else {
                blockCanvas.getJComponent().setPreferredSize(new Dimension(
                        blockCanvas.getWidth() - widget.getJComponent().getWidth(),
                        blockCanvasLayer.getHeight()));
            }
        }
        boolean success = workspaceWidgets.add(widget);
        if (!success) {
            System.err.println("not able to add: " + widget);
        }
    }

    /**
     * Removes the specified widget from this Workspace
     * @param widget the desired widget to remove
     */
    public void removeWidget(WorkspaceWidget widget) {
        workspaceWidgets.remove(widget);
        this.remove((JComponent) widget);
    }

    /**
     * Returns an unmodifiable Iterable over all the WorkspaceWidgets
     * @return an unmodifiable Iterable over all the WorkspaceWidgets
     */
    public Iterable<WorkspaceWidget> getWorkspaceWidgets() {
        return Collections.unmodifiableSet(workspaceWidgets);
    }

    /**
     * Returns the set of all RenderableBlocks in the Workspace.
     * Includes all live blocks on all pages. Does NOT include:
     *    (1) Factory blocks,
     *    (2) dead blocks,
     *    (3) or subset blocks.
     *  If no blocks are found, it returns an empty set.
     * @return all the RenderableBlocks in the Workspace
     *        or an empty set if none exists.
     */
    public Iterable<RenderableBlock> getRenderableBlocks() {
        //TODO: performance issue, must iterate through all blocks
        return blockCanvas.getBlocks();
    }

    /**
     * Returns the set of all Blocks in the Workspace.
     * Includes all live blocks on all pages. Does NOT include:
     *    (1) Factory blocks,
     *    (2) dead blocks,
     *    (3) or subset blocks.
     *  If no blocks are found, it returns an empty set.
     * @return all the Blocks in the Workspace
     *        or an empty set if none exists.
     */
    public Iterable<Block> getBlocks() {
        //TODO: performance issue, must iterate through all blocks
        final ArrayList<Block> blocks = new ArrayList<Block>();
        for (final RenderableBlock renderable : blockCanvas.getBlocks()) {
            blocks.add(getEnv().getBlock(renderable.getBlockID()));
        }
        return blocks;
    }

    /**
     * Returns all the RenderableBlocks of the specified genus.
     * Include all live blocks on all pages.  Does NOT include:
     *    (1) all blocks of a different genus
     *    (2) Factory blocks,
     *    (3) dead blocks,
     *    (4) or subset blocks.
     *  If no blocks are found, it returns an empty set.
     * @param genusName - the genus name of the blocks to return
     * @return all the RenderableBlocks of the specified genus
     *        or an empty set if none exists.
     */
    public Iterable<RenderableBlock> getRenderableBlocksFromGenus(String genusName) {
        //TODO: performance issue, must iterate through all blocks
        ArrayList<RenderableBlock> blocks = new ArrayList<RenderableBlock>();
        for (RenderableBlock block : blockCanvas.getBlocks()) {
            if (getEnv().getBlock(block.getBlockID()).getGenusName().equals(genusName)) {
                blocks.add(block);
            }
        }
        return blocks;
    }

    /**
     * Returns all the Blocks of the specified genus.
     * Include all live blocks on all pages.  Does NOT include:
     *    (1) all blocks of a different genus
     *    (2) Factory blocks,
     *    (3) dead blocks,
     *    (4) or subset blocks.
     *  If no blocks are found, it returns an empty set.
     * @param genusName - the genus name of the blocks to return
     * @return all the Blocks of the specified genus
     *        or an empty set if none exists.
     */
    public Iterable<Block> getBlocksFromGenus(String genusName) {
        //TODO: performance issue, must iterate through all blocks
        ArrayList<Block> blocks = new ArrayList<Block>();
        for (RenderableBlock renderable : blockCanvas.getBlocks()) {
            Block block = getEnv().getBlock(renderable.getBlockID());
            if (block.getGenusName().equals(genusName)) {
                blocks.add(block);
            }
        }
        return blocks;
    }

    /**
     * Returns the top level blocks in the Workspace (blocks that are
     * parents of stacks)
     * @return the top level blocks in the Workspace
     */
    public Iterable<RenderableBlock> getTopLevelBlocks() {
        return blockCanvas.getTopLevelBlocks();
    }

    /**
     * Cleans up all the blocks within the block canvas using the default
     * arrangement algorithm.
     * TODO ria for now its the naive arranger that uses just the y-coor.
     */
    public void cleanUpAllBlocks() {
        blockCanvas.arrangeAllBlocks();
    }

    /**
     * calls TypeBlockManager to copy the highlighted blocks on the canvas
     */
    public void copyBlocks() {
        typeBlockManager.copyBlock(this);
    }

    /**
     * calls TypeBlockManager to pastes the highlighted blocks on the canvas
     */
    public void pasteBlocks() {
        typeBlockManager.pasteBlock(this);
    }

    //////////////////////////
    // WORKSPACE LISTENERS
    //////////////////////////
    /**
     * Adds the specified WorkspaceListener
     */
    public void addWorkspaceListener(WorkspaceListener listener) {
        if (listener != null) {
            // warn of duplicate adds
            assert (!workspaceListeners.contains(listener)) : "WorkspaceListener " + listener.toString() + " has already been added.";
            workspaceListeners.add(listener);
        }
    }

    /**
     * Removes the specified WorkspaceListener
     * @param listener
     */
    public void removeWorkspaceListener(WorkspaceListener listener) {
        if (listener != null) {
            workspaceListeners.remove(listener);
        }
    }

    /**
     * Notifies all Workspace listeners of the workspace event
     * @param event
     */
    public void notifyListeners(WorkspaceEvent event) {
        for (WorkspaceListener wl : workspaceListeners) {
            wl.workspaceEventOccurred(event);
        }
    }

    ////////////////////
    //TypeBlockManaging
    ////////////////////
    /**
     * Enables TypeBLocking if and only if enabled == true
     */
    public void enableTypeBlocking(boolean enabled) {
        typeBlockManager.setEnabled(enabled);
    }

    /**
     * The type block manager, if defined.
     * @return The manager.
     * @see #enableTypeBlocking(boolean)
     */
    public TypeBlockManager getTypeBlockManager() {
        return typeBlockManager;
    }

    ///////////////////
    // WORKSPACE ZOOM
    ///////////////////
    private double zoom = 1.0;

    /**
     * Sets the Workspace zoom at the specified zoom level
     * @param newZoom the desired zoom level
     */
    public void setWorkspaceZoom(double newZoom) {
        double oldZoom = this.zoom;
        int cDX = 0, cDY = 0;

        this.zoom = newZoom;

        BlockUtilities.setZoomLevel(newZoom);
        for (RenderableBlock block : getRenderableBlocks()) {
            block.setZoomLevel(newZoom);
        }
        for (RenderableBlock block : getFactoryManager().getBlocks()) {
            block.setZoomLevel(newZoom);
        }
        for (Page p : getBlockCanvas().getPages()) {
            for (RenderableBlock block : p.getTopLevelBlocks()) {

                // checks if the x and y position has not been set yet, this happens when
                // a previously saved project is just opened and the blocks have not been
                // moved yet. otherwise, the unzoomed X and Y are calculated in RenderableBlock
                if (block.getUnzoomedX() == 0.0 && block.getUnzoomedY() == 0.0) {
                    if (newZoom == 1.0) {
                        block.setUnzoomedX(block.getX());
                        block.setUnzoomedY(block.getY());
                    } else {
                        block.setUnzoomedX(block.calculateUnzoomedX(block.getX()));
                        block.setUnzoomedY(block.calculateUnzoomedY(block.getY()));
                    }
                } else {
                }


                if (block.hasComment()) {
                    //determine the new relative position of the comment based on the current relative position
                    cDX = (int) ((block.getComment().getX() - block.getX()) / oldZoom * newZoom);
                    cDY = (int) ((block.getComment().getY() - block.getY()) / oldZoom * newZoom);
                }
                // calculates the new position based on the initial position when zoom is at 1.0
                block.setLocation((int) (block.getUnzoomedX() * this.zoom), (int) (block.getUnzoomedY() * this.zoom));
                if (block.hasComment()) {
                    //Set the comment location to the new relative position
                    block.getComment().setLocation(block.getX() + cDX, block.getY() + cDY);
                }
                block.redrawFromTop();
                block.repaint();
            }
        }
        Page.setZoomLevel(newZoom);
    }

    /**
     * Returns the current workspace zoom
     * @return the current workspace zoom
     */
    public double getCurrentWorkspaceZoom() {
        return zoom;
    }

    /**
     * Resets the workspace zoom to the default level
     *
     */
    public void setWorkspaceZoomToDefault() {
        this.setWorkspaceZoom(1.0);
    }

    public void scrollToComponent(JComponent c) {
        blockCanvas.scrollToComponent(c);
    }

    public void stateChanged(ChangeEvent e) {
        List<Explorer> explorers = factory.getNavigator().getExplorers();
        for (Explorer exp : explorers) {
            List<Page> leftMostPages = blockCanvas.getLeftmostPages(exp.getSelectedCanvasWidth());
            boolean expanded = exp.anyCanvasSelected();
            for (Page p : blockCanvas.getPages()) {
                if (expanded) {
                    p.setHide(false);
                }
            }
            for (Page p : leftMostPages) {
                if (expanded) {
                    p.setHide(true);
                }
            }
        }
    }

    ////////////////
    //PAGE METHODS (note: these may change)
    ////////////////
    /**
     * Adds the specified page to the Workspace at the right end of the canvas
     * @param page the desired page to add
     */
    public void addPage(Page page) {
        addPage(page, blockCanvas.numOfPages());
    }

    /**
     * Adds the specified page to the Workspace at the specified position on the canvas
     * @param page the desired page to add
     */
    public void addPage(Page page, int position) {
        //this method assumes that this addPage was a user or file loading
        //event in which case a page added event should be thrown
        addPageAt(page, position, true);
    }

    /**
     * Places the specified page at the specified index.
     * If a page already exists at that index,
     * this method will replace it.
     * @param page the Page to place
     * @param position - the position to place the specified page,
     *       where 0 is the leftmost page
     */
    public void putPage(Page page, int position) {
        if (blockCanvas.hasPageAt(position)) {
            removePageAt(position);
        }
        addPageAt(page, blockCanvas.numOfPages(), true);
    }

    /**
     * Adds a Page in the specified position, where position 0 is the leftmost page
     * @param page - the desired Page to add
     * @param index - the desired position of the page
     * @param fireWorkspaceEvent if set to true, will fire a WorkspaceEvent that a
     * Page was added
     */
    private void addPageAt(Page page, int index, boolean fireWorkspaceEvent) {
        blockCanvas.addPage(page, index);
        workspaceWidgets.add(page);
        if (fireWorkspaceEvent) {
            notifyListeners(new WorkspaceEvent(this, page, WorkspaceEvent.PAGE_ADDED));
        }
    }

    /**
     * Removes the specified page from the Workspace at the specified position,
     * where position 0 is the left most page
     * @param position
     */
    public void removePageAt(int position) {
        removePage(blockCanvas.getPageAt(position));
    }

    /**
     * Removes the specified page from the Workspace
     * @param page the desired page to remove
     */
    public void removePage(Page page) {
        boolean success = workspaceWidgets.remove(page);
        if (!success) {
            System.out.println("Page: " + page + ", was NOT removed successfully");
        }
        notifyListeners(new WorkspaceEvent(this, page, WorkspaceEvent.PAGE_REMOVED));
        blockCanvas.removePage(page);
    }

    /**
     * Renames the page with the specified oldName to the specified newName.
     * @param oldName the oldName of the page to rename
     * @param newName the String name to change the page name to
     */
    public void renamePage(String oldName, String newName) {
        Page renamedPage = blockCanvas.renamePage(oldName, newName);
        //TODO ria HACK TO GET DRAWERS AND PAGE IN SYNC
        //as a rule, all relevant data like pages and drawers should be updated before
        //an event is released because the listeners make assumptions on the state
        //of the data.  in the future, have the page rename its drawer
        factory.renameDynamicDrawer(oldName, newName);
        notifyListeners(new WorkspaceEvent(this, renamedPage, oldName, WorkspaceEvent.PAGE_RENAMED));
    }

    /**
     * Returns the number of pages contained within this.  By default
     * will always have a page even if a page was not specified.  The page
     * will just be blank.
     * @return the number of pages contained within this
     */
    public int getNumPages() {
        return blockCanvas.numOfPages();
    }

    /**
     * Find the page that lies underneath this block
     * CAN RETURN NULL
     * @param block
     */
    public Page getCurrentPage(RenderableBlock block) {
        for (Page page : getBlockCanvas().getPages()) {
            if (page.contains(SwingUtilities.convertPoint(block.getParent(), block.getLocation(), page.getJComponent()))) {
                return page;
            }
        }
        return null;
    }

    /**
     * Marks the page of the specified name as being selected.  The workspace
     * view may shift to that page.
     * @param page the Page selected
     * @param byUser true if Page was selected by the User
     */
    public void pageSelected(Page page, boolean byUser) {
        blockCanvas.switchViewToPage(page);
    }

    public FactoryManager getFactoryManager() {
        return factory;
    }

    /**
     * Returns an unmodifiable Iterable of all the SearchableContainers within this
     * workspace.
     */
    public Iterable<SearchableContainer> getAllSearchableContainers() {
        ArrayList<SearchableContainer> containers = new ArrayList<SearchableContainer>(factory.getSearchableContainers());

        for (WorkspaceWidget w : workspaceWidgets) {
            if (w instanceof SearchableContainer) {
                containers.add((SearchableContainer) w);
            }
        }

        return Collections.unmodifiableList(containers);
    }

    ////////////////////////
    //Subsets             //
    ////////////////////////
    /**
     * Sets up the subsets by clearing all subsets and installing
     * the new collection of subsets.  If "usingSys" is true,
     * the the factory and myblocks drawers will be accessible.
     * If "usingSubs" is true, then the subset drawers will
     * be accessible.
     * @param subsets - collection of subsets
     * @param usingSys - true for factory and myblocks
     * @param usingSubs - true for subsets
     */
    public void setupSubsets(Collection<Subset> subsets, boolean usingSys, boolean usingSubs) {
        this.factory.setupSubsets(subsets, usingSys, usingSubs);
    }

    ////////////////////////
    // SAVING AND LOADING //
    ////////////////////////
    /**
     * Returns the node of this.  Currently returns the BlockCanvas
     * node only.
     * @return the node of this.
     */
    public Node getSaveNode(Document document) {
      return blockCanvas.getSaveNode(document);
    }

    /**
     * Loads the workspace with the following content:
     * - RenderableBlocks and their associated Block instances that reside
     *   within the BlockCanvas
     * @param newRoot the XML Element containing the new desired content.  Some of the
     * content in newRoot may override the content in originalLangRoot.  (For now,
     * pages are automatically overwritten.  In the future, will allow drawers
     * to be optionally overriden or new drawers to be inserted.)
     * @param originalLangRoot the original language/workspace specification content
     * @requires originalLangRoot != null
     */
    public void loadWorkspaceFrom(Element newRoot, Element originalLangRoot) {
        if (newRoot != null) {
            //load pages, page drawers, and their blocks from save file
            blockCanvas.loadSaveString(newRoot);
            //load the block drawers specified in the file (may contain
            //custom drawers) and/or the lang def file if the contents specify
            PageDrawerLoadingUtils.loadBlockDrawerSets(this, originalLangRoot, factory);
            PageDrawerLoadingUtils.loadBlockDrawerSets(this, newRoot, factory);
            loadWorkspaceSettings(newRoot);
        } else {
            //load from original language/workspace root specification
            blockCanvas.loadSaveString(originalLangRoot);
            //load block drawers and their content
            PageDrawerLoadingUtils.loadBlockDrawerSets(this, originalLangRoot, factory);
            loadWorkspaceSettings(originalLangRoot);
        }

    }

    /**
     * Loads the settings for this Workspace.  Settings include
     * specification of programming environment features such as
     * the search bar, minimap, or zooming.
     * @param root
     */
    private void loadWorkspaceSettings(Element root) {
        Pattern attrExtractor = Pattern.compile("\"(.*)\"");
        Matcher nameMatcher;

        NodeList miniMapNodes = root.getElementsByTagName("MiniMap");
        Node miniMapNode;
        for (int i = 0; i < miniMapNodes.getLength(); i++) {
            miniMapNode = miniMapNodes.item(i);
            if (miniMapNode.getNodeName().equals("MiniMap")) {
                nameMatcher = attrExtractor.matcher(miniMapNode.getAttributes().getNamedItem("enabled").toString());
                if (nameMatcher.find() && nameMatcher.group(1).equals("no")) {
                    this.disableMiniMap();
                }
            }
        }

        NodeList typeNodes = root.getElementsByTagName("Typeblocking");
        Node typeNode;
        for (int i = 0; i < typeNodes.getLength(); i++) {
            typeNode = typeNodes.item(i);
            if (typeNode.getNodeName().equals("Typeblocking")) {
                nameMatcher = attrExtractor.matcher(typeNode.getAttributes().getNamedItem("enabled").toString());
                if (nameMatcher.find() && nameMatcher.group(1).equals("no")) {
                    this.enableTypeBlocking(false);
                } else {
                    this.enableTypeBlocking(true);
                }
            }
        }
    }

    /**
     * Clears the Workspace of:
     * - all the live blocks in the BlockCanvas.
     * - all the pages on the BlockCanvas
     * - all its BlockDrawers and the RB's that reside within them
     * - clears all the BlockDrawer bars of its drawer references and
     *   their associated buttons
     * - clears all RenderableBlock instances (which clears their associated
     *   Block instances.)
     * Note: we want to get rid of all RendereableBlocks and their
     * references.
     *
     * Want to get the Workspace ready to load another workspace
     */
    public void reset() {
        //we can't iterate and remove widgets at the same time so
        //we remove widgets after we've collected all the widgets we want to remove
        //TreeSet.remove() doesn't always work on the TreeSet, so instead,
        //we clear and re-add the widgets we want to keep
        ArrayList<WorkspaceWidget> widgetsToRemove = new ArrayList<WorkspaceWidget>();
        ArrayList<WorkspaceWidget> widgetsToKeep = new ArrayList<WorkspaceWidget>();
        for (WorkspaceWidget w : workspaceWidgets) {
            if (w instanceof Page) {
                widgetsToRemove.add(w);
            } else {
                widgetsToKeep.add(w);
            }
        }
        workspaceWidgets.clear();
        workspaceWidgets.addAll(widgetsToKeep);
        workspaceWidgets.add(factory);

        //We now reset the widgets we removed.
        //Doing this for each one gets costly.
        //Do not do this for Pages because on repaint,
        //the Page tries to access its parent.
        for (WorkspaceWidget w : widgetsToRemove) {
            Container parent = w.getJComponent().getParent();
            if (w instanceof Page) {
                ((Page) w).reset();
            }
            if (parent != null) {
                parent.remove(w.getJComponent());
                parent.validate();
                parent.repaint();
            }
        }

        //We now reset, the blockcanvas, the factory, and the renderableblocks
        blockCanvas.reset();
        addPageAt(Page.getBlankPage(this), 0, false); //TODO: System expects PAGE_ADDED event
        factory.reset();

        env.resetAll();

        revalidate();
    }

    /***********************************
     * State Saving Stuff for Undo/Redo *
     ***********************************/
    private class WorkspaceState {

        public Map<Long, Object> blockStates;
        public Object blockCanvasState;
    }

    public Object getState() {
        return null;
    }

    public void loadState(Object memento) {
        assert memento instanceof WorkspaceState : "";
        if (memento instanceof WorkspaceState) {
            WorkspaceState state = (WorkspaceState) memento;
            //Load the blocks state
            for (Long blockID : state.blockStates.keySet()) {
                Block toBeUpdated = getEnv().getBlock(blockID);
                toBeUpdated.loadState(state.blockStates.get(blockID));
            }
            //Load the canvas state
            blockCanvas.loadState(state.blockCanvasState);
        }
    }

    public void undo() {
    }

    public void redo() {
    }

    /******************************************
     * RBParent implemented methods
     ******************************************/
    public void addToBlockLayer(Component c) {
        this.add(c, DRAGGED_BLOCK_LAYER);
    }

    public void addToHighlightLayer(Component c) {
        this.add(c, DRAGGED_BLOCK_HIGHLIGHT_LAYER);
    }
}
TOP

Related Classes of edu.mit.blocks.workspace.Workspace

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.