com.algorithmstudy.visualizer.client.sort.SortingDisplayPanel.java Source code

Java tutorial

Introduction

Here is the source code for com.algorithmstudy.visualizer.client.sort.SortingDisplayPanel.java

Source

/*
 * Copyright (C) 2009 The Algorithm Study Project
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * version 2.0 as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License version
 * 2.0 along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
 * or find it on the GNU website at:
 *
 *    http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
 */
package com.algorithmstudy.visualizer.client.sort;

import java.util.LinkedList;

import com.algorithmstudy.visualizer.client.model.ListBasedAlgorithm;
import com.algorithmstudy.visualizer.client.model.VisualizerTimer;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.ChangeListener;
import com.google.gwt.user.client.ui.ClickListener;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.Panel;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;

/**
 * {@code SortingDisplayPanel} contains the code to render and manage the display of the
 * algorithm(s) being visualized.
 */
public class SortingDisplayPanel extends VerticalPanel {

    /**
     * {@code VisualizerIndex} is used to indicate how many algorithms should be displayed.
     */
    public static enum VisualizerIndex {
        // NOTE: The name VisualizerIndex is not a good name; figure out a better name.
        First, Second
    }

    /**
     * {@code NumberElements} encodes the possible number of elements that can be selected for
     * sorting.
     */
    private static enum NumberElements {
        Fifty(50, "50", 0), SeventyFive(75, "75", 1), OneHundred(100, "100", 2), OneHundredFifty(150, "150",
                3), TwoHundred(200, "200", 4), ThreeHundred(300, "300", 5), FourHundred(400, "400", 6);

        private int number;
        private String numberStr;
        private int index;

        private NumberElements(int number, String numberStr, int index) {
            this.number = number;
            this.numberStr = numberStr;
            this.index = index;
        }

        public int getNumber() {
            return number;
        }

        public String getNumberAsString() {
            return numberStr;
        }

        public int getIndex() {
            return index;
        }

        /**
         * Gets the correct instance of {@code NumberElements} for the specified index.
         * 
         * @param index
         *          The index for which to get an instance of {@code NumberElements}.
         * @return The correct instance of {@code NumberElements} for the specified index or null if the
         *         index does not correspond to an instance of {@code NumberElements}.
         */
        public static NumberElements getNumberElementsForIndex(int index) {
            if (Fifty.getIndex() == index) {
                return Fifty;
            } else if (SeventyFive.getIndex() == index) {
                return SeventyFive;
            } else if (OneHundred.getIndex() == index) {
                return OneHundred;
            } else if (OneHundredFifty.getIndex() == index) {
                return OneHundredFifty;
            } else if (TwoHundred.getIndex() == index) {
                return TwoHundred;
            } else if (ThreeHundred.getIndex() == index) {
                return ThreeHundred;
            } else if (FourHundred.getIndex() == index) {
                return FourHundred;
            }
            return null;
        }

    }

    private static final int DEFAULT_SPEED = 50;
    private static final NumberElements DEFAULT_NUM_ELEMENTS = NumberElements.TwoHundred;

    private SortControlsBar sortControls;

    private VisualizerIndex algorithmsDisplayed;

    private BarChartVisualizer visualizer1;
    private ListBasedAlgorithm alg1;
    private SortAlgorithmType alg1Type;

    private BarChartVisualizer visualizer2;
    private ListBasedAlgorithm alg2;
    private SortAlgorithmType alg2Type;

    private int[] valuesToSort;

    private VisualizerTimer timer;

    /**
     * Create a new {@code SortingDisplayPanel} instance.
     */
    public SortingDisplayPanel() {
        setWidth("100%");

        timer = new VisualizerTimerImpl();

        generateValuesToSort(DEFAULT_NUM_ELEMENTS);

        algorithmsDisplayed = VisualizerIndex.First;

        sortControls = new SortControlsBar(this, DEFAULT_NUM_ELEMENTS, algorithmsDisplayed);

        doReinitForOne(SortAlgorithmType.BubbleSort);
    }

    /**
     * Sets the algorithm type for the specified visualizer.
     * 
     * @param vi
     *          The index of the visualizer to set.
     * @param algorithm
     *          The algorithm to show in the visualizer.
     * @throws IllegalStateException
     *           Thrown if the specified {@code VisualizerIndex} is the second algorithm but only one
     *           algorithm is displayed.
     */
    protected void setVisualizerAlgorithm(VisualizerIndex vi, SortAlgorithmType algorithm) {
        if (VisualizerIndex.Second == vi && VisualizerIndex.First == algorithmsDisplayed) {
            throw new IllegalStateException(
                    "Can't set the second algorithm when only one algorithm is" + " displayed.");
        }

        timer.stop();

        ListBasedAlgorithm a = algorithm.getInstance();
        if (VisualizerIndex.First == vi) {
            alg1 = a;
            visualizer1.setAlgorithm(a);
            alg1Type = algorithm;
        } else {
            alg2 = a;
            visualizer2.setAlgorithm(a);
            alg2Type = algorithm;
        }

        reinit();
    }

    /**
     * Reinitializes the display panel using the existing number and type of algorithms that are
     * displayed.
     */
    public void reinit() {
        reinit(algorithmsDisplayed, alg1Type, alg2Type);
    }

    /**
     * Reinitializes the display panel using the specified number of algorithms to display.
     * 
     * @param vi
     *          The number of algorithms to display.
     */
    public void reinit(VisualizerIndex vi, SortAlgorithmType algorithm1, SortAlgorithmType algorithm2) {
        if (null == vi) {
            throw new NullPointerException("VisualizerIndex parameter required; can not be null.");
        }

        if (null == algorithm1) {
            throw new NullPointerException("At least one algorithm must be specified.");
        }

        if (VisualizerIndex.Second == vi && null == algorithm2) {
            throw new NullPointerException(
                    "Two algorithms must be specified when setting two " + "algorithms to be displayed.");
        }

        this.algorithmsDisplayed = vi;

        if (VisualizerIndex.First == algorithmsDisplayed) {
            doReinitForOne(algorithm1);
        } else {
            doReinitForTwo(algorithm1, algorithm2);
        }
    }

    private void doReinitForOne(SortAlgorithmType algorithm) {
        clearForReuse();

        sortControls.doReset();

        setHorizontalAlignment(VerticalPanel.ALIGN_LEFT);

        sortControls.doSetNumElementsCB(VisualizerIndex.First);
        this.add(sortControls);

        setHorizontalAlignment(VerticalPanel.ALIGN_CENTER);

        visualizer1 = new BarChartVisualizer(valuesToSort, true);
        alg1 = algorithm.getInstance();
        alg1.setVisualizer(visualizer1);
        alg1.setListToSort(cloneValuesToSort());
        visualizer1.setAlgorithm(alg1);
        timer.addVisualizer(visualizer1);

        alg1Type = algorithm;

        visualizer2 = null;
        alg2 = null;
        alg2Type = null;

        this.add(visualizer1);
    }

    private void doReinitForTwo(SortAlgorithmType algorithm1, SortAlgorithmType algorithm2) {
        clearForReuse();

        sortControls.doReset();

        setHorizontalAlignment(VerticalPanel.ALIGN_LEFT);

        sortControls.doSetNumElementsCB(VisualizerIndex.Second);
        this.add(sortControls);

        setHorizontalAlignment(VerticalPanel.ALIGN_CENTER);

        visualizer1 = new BarChartVisualizer(valuesToSort, false);
        alg1 = algorithm1.getInstance();
        alg1.setVisualizer(visualizer1);
        alg1.setListToSort(cloneValuesToSort());
        visualizer1.setAlgorithm(alg1);
        timer.addVisualizer(visualizer1);

        alg1Type = algorithm1;

        visualizer2 = new BarChartVisualizer(valuesToSort, false);
        alg2 = algorithm2.getInstance();
        alg2.setVisualizer(visualizer2);
        alg2.setListToSort(cloneValuesToSort());
        visualizer2.setAlgorithm(alg2);
        timer.addVisualizer(visualizer2);

        alg2Type = algorithm2;

        HorizontalPanel visPanel = new HorizontalPanel();
        visPanel.setHorizontalAlignment(HorizontalPanel.ALIGN_CENTER);

        visPanel.add(visualizer1);
        visPanel.add(visualizer2);

        this.add(visPanel);
    }

    /**
     * Create a copy of the {@code valuesToSort} array.
     * 
     * @return A copy of the {@code valuesToSort} array.
     */
    private int[] cloneValuesToSort() {
        int[] theCopy = new int[valuesToSort.length];
        for (int i = 0; i < valuesToSort.length; i++) {
            theCopy[i] = valuesToSort[i];
        }
        return theCopy;
    }

    /**
     * Generates the set of values that are to be sorted.
     * 
     * @param numValues
     *          The number of values that should be generated.
     */
    private void generateValuesToSort(NumberElements numValues) {
        // TODO: improve on the algorithm used to generate this randomized ordering
        valuesToSort = new int[numValues.getNumber()];
        LinkedList<Integer> l = new LinkedList<Integer>();
        for (int i = 1; i <= numValues.getNumber(); i++) {
            l.add(i);
        }

        for (int i = numValues.getNumber(); i > 0; i--) {
            int pos = (int) ((i + 1) * Math.random());
            if (l.size() == pos) {
                --pos;
            }
            Integer value = l.remove(pos);
            valuesToSort[numValues.getNumber() - i] = value;
        }
    }

    /**
     * Clear the widgets from this display panel.
     */
    private void clearForReuse() {
        stopTimer();
        timer.clearVisualizerList();

        for (int i = getWidgetCount() - 1; i >= 0; i--) {
            remove(i);
        }
    }

    /**
     * Starts the timer.
     */
    private void startTimer() {
        timer.start();
    }

    /**
     * Stops the timer.
     */
    private void stopTimer() {
        timer.stop();
    }

    /**
     * Set the multiplier for the timer.
     * 
     * @param multiplier
     *          The multiplier to set for the timer.
     */
    private void changeTimerMultiplier(int multiplier) {
        timer.setTickMultiplier(multiplier);
    }

    /**
     * A container to hold the sorting controls. The controls consist of the speed of execution, the
     * number of elements being put into the algorithm resetting the visualizations and randomizing
     * the order of the elements.
     */
    private class SortControlsBar extends HorizontalPanel {

        private SortingDisplayPanel parentPanel;

        private TextBox speedTA;
        private int lastSpeed;

        private ListBox numElementsSingleLB;
        private ListBox numElementsDoubleLB;
        private ChangeListener numElementsChangeListener;
        private NumberElements lastNumElements;

        private Button goButton;
        private Button stopButton;
        private Button resetButton;

        private Button randomize;

        public SortControlsBar(SortingDisplayPanel parent, NumberElements numElements,
                SortingDisplayPanel.VisualizerIndex numVisualizersDisplayed) {
            parentPanel = parent;

            setHorizontalAlignment(HorizontalPanel.ALIGN_LEFT);

            // Add the speed text box
            Label speedLabel = new Label("Speed");
            this.add(speedLabel);

            speedTA = new TextBox();
            speedTA.setText(Integer.toString(DEFAULT_SPEED));
            speedTA.setTitle("Sort Speed: 1 - 100");
            speedTA.setMaxLength(3);
            speedTA.setVisibleLength(3);
            speedTA.addChangeListener(new ChangeListener() {
                public void onChange(Widget w) {
                    doChangeSpeed(w);
                }
            });
            this.add(speedTA);

            this.add(createSpacer());

            // Add the number of elements selection list box
            Label numElementsLabel = new Label("# Elements");
            this.add(numElementsLabel);

            numElementsSingleLB = new ListBox();
            numElementsSingleLB.addItem(NumberElements.Fifty.getNumberAsString());
            numElementsSingleLB.addItem(NumberElements.SeventyFive.getNumberAsString());
            numElementsSingleLB.addItem(NumberElements.OneHundred.getNumberAsString());
            numElementsSingleLB.addItem(NumberElements.OneHundredFifty.getNumberAsString());
            numElementsSingleLB.addItem(NumberElements.TwoHundred.getNumberAsString());
            numElementsSingleLB.addItem(NumberElements.ThreeHundred.getNumberAsString());
            numElementsSingleLB.addItem(NumberElements.FourHundred.getNumberAsString());
            numElementsSingleLB.setSelectedIndex(numElements.index);

            numElementsDoubleLB = new ListBox();
            numElementsDoubleLB.addItem(NumberElements.Fifty.getNumberAsString());
            numElementsDoubleLB.addItem(NumberElements.SeventyFive.getNumberAsString());
            numElementsDoubleLB.addItem(NumberElements.OneHundred.getNumberAsString());
            numElementsDoubleLB.addItem(NumberElements.OneHundredFifty.getNumberAsString());
            numElementsDoubleLB.addItem(NumberElements.TwoHundred.getNumberAsString());
            numElementsDoubleLB.setSelectedIndex(numElements.index);

            lastNumElements = numElements;

            numElementsChangeListener = new ChangeListener() {
                public void onChange(Widget w) {
                    doChangeNumElements(w);
                }
            };

            if (SortingDisplayPanel.VisualizerIndex.Second == numVisualizersDisplayed) {
                numElementsDoubleLB.addChangeListener(numElementsChangeListener);
                numElementsSingleLB.setVisible(false);
            } else {
                numElementsSingleLB.addChangeListener(numElementsChangeListener);
                numElementsDoubleLB.setVisible(false);
            }
            this.add(numElementsSingleLB);
            this.add(numElementsDoubleLB);

            this.add(createSpacer());

            // Add the go, stop and reset buttons
            goButton = new Button("Go");
            goButton.setWidth("5em");
            goButton.addClickListener(new ClickListener() {
                public void onClick(Widget w) {
                    doGo(w);
                }
            });
            this.add(goButton);

            stopButton = new Button("Stop");
            stopButton.setWidth("5em");
            stopButton.setVisible(false);
            stopButton.addClickListener(new ClickListener() {
                public void onClick(Widget w) {
                    doStop(w);
                }
            });
            this.add(stopButton);

            resetButton = new Button("Reset");
            resetButton.setWidth("5em");
            resetButton.setVisible(false);
            resetButton.addClickListener(new ClickListener() {
                public void onClick(Widget w) {
                    doReset(w);
                }
            });
            this.add(resetButton);

            this.add(createSpacer());

            // Add the randomize button
            randomize = new Button("Randomize");
            randomize.addClickListener(new ClickListener() {
                public void onClick(Widget w) {
                    doRandomize(w);
                }
            });
            this.add(randomize);
        }

        private Panel createSpacer() {
            VerticalPanel p = new VerticalPanel();
            p.setSize("2em", ".5em");
            return p;
        }

        private void doChangeSpeed(Widget w) {
            if (speedTA != w) {
                return;
            }

            int newSpeed = VisualizerTimerImpl.MULTIPLIER_DEFAULT;
            try {
                newSpeed = Integer.parseInt(speedTA.getText());
            } catch (NumberFormatException e) {
                // TODO: warn user that the speed must be a number > 0 and < 101.
                // NOTE: That warning may not be necessary. The mouse over info and setting the value to a
                // value in the acceptable range may be sufficient.
                speedTA.setText(Integer.toString(lastSpeed));
                return;
            }

            if (VisualizerTimerImpl.MULTIPLIER_LOW > newSpeed) {
                newSpeed = VisualizerTimerImpl.MULTIPLIER_LOW;
                speedTA.setText(Integer.toString(VisualizerTimerImpl.MULTIPLIER_LOW));
            } else if (VisualizerTimerImpl.MULTIPLIER_HIGH < newSpeed) {
                newSpeed = VisualizerTimerImpl.MULTIPLIER_HIGH;
                speedTA.setText(Integer.toString(VisualizerTimerImpl.MULTIPLIER_HIGH));
            }

            parentPanel.changeTimerMultiplier(newSpeed);

            lastSpeed = newSpeed;
        }

        private void doChangeNumElements(Widget w) {
            if (numElementsSingleLB != w && numElementsDoubleLB != w) {
                return;
            }

            NumberElements newNumElements = null;
            if (numElementsSingleLB == w) {
                newNumElements = NumberElements.getNumberElementsForIndex(numElementsSingleLB.getSelectedIndex());
                numElementsDoubleLB.setSelectedIndex(newNumElements.getIndex());
            } else {
                newNumElements = NumberElements.getNumberElementsForIndex(numElementsDoubleLB.getSelectedIndex());
                numElementsSingleLB.setSelectedIndex(newNumElements.getIndex());
            }

            if (newNumElements == lastNumElements) {
                return;
            }

            lastNumElements = newNumElements;

            parentPanel.generateValuesToSort(lastNumElements);
            parentPanel.reinit();
        }

        private void doGo(Widget w) {
            if (goButton != w) {
                return;
            }
            doGo();
        }

        protected void doGo() {
            goButton.setVisible(false);
            stopButton.setVisible(true);
            resetButton.setVisible(false);

            parentPanel.startTimer();
        }

        private void doStop(Widget w) {
            if (stopButton != w) {
                return;
            }
            doStop();
        }

        protected void doStop() {
            goButton.setVisible(false);
            stopButton.setVisible(false);
            resetButton.setVisible(true);

            parentPanel.stopTimer();
        }

        private void doReset(Widget w) {
            if (resetButton != w) {
                return;
            }
            doReset();

            parentPanel.reinit();
        }

        protected void doReset() {
            goButton.setVisible(true);
            stopButton.setVisible(false);
            resetButton.setVisible(false);
        }

        private void doRandomize(Widget w) {
            if (randomize != w) {
                return;
            }

            parentPanel.generateValuesToSort(lastNumElements);
            parentPanel.reinit();
        }

        protected void doSetNumElementsCB(SortingDisplayPanel.VisualizerIndex vi) {
            if (SortingDisplayPanel.VisualizerIndex.First == vi) {
                numElementsDoubleLB.removeChangeListener(numElementsChangeListener);
                numElementsDoubleLB.setVisible(false);
                numElementsSingleLB.setVisible(true);
                numElementsSingleLB.addChangeListener(numElementsChangeListener);
            } else if (SortingDisplayPanel.VisualizerIndex.Second == vi) {
                numElementsSingleLB.removeChangeListener(numElementsChangeListener);
                numElementsSingleLB.setVisible(false);

                if (lastNumElements.getIndex() > SortingDisplayPanel.NumberElements.TwoHundred.getIndex()) {
                    numElementsSingleLB.setSelectedIndex(SortingDisplayPanel.NumberElements.TwoHundred.getIndex());
                    numElementsDoubleLB.setSelectedIndex(SortingDisplayPanel.NumberElements.TwoHundred.getIndex());
                    lastNumElements = SortingDisplayPanel.NumberElements.TwoHundred;
                    parentPanel.generateValuesToSort(lastNumElements);
                }

                numElementsDoubleLB.setVisible(true);
                numElementsDoubleLB.addChangeListener(numElementsChangeListener);
            }
        }
    }

}