edu.ycp.cs.netcoder.client.CourseAndProblemView.java Source code

Java tutorial

Introduction

Here is the source code for edu.ycp.cs.netcoder.client.CourseAndProblemView.java

Source

// NetCoder - a web-based pedagogical programming environment
// Copyright (C) 2011, Jaime Spacco <jspacco@knox.edu>
// Copyright (C) 2011, David H. Hovemeyer <dhovemey@ycp.edu>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

package edu.ycp.cs.netcoder.client;

import java.util.Set;

import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.logical.shared.SelectionEvent;
import com.google.gwt.event.logical.shared.SelectionHandler;
import com.google.gwt.gen2.table.client.FixedWidthFlexTable;
import com.google.gwt.gen2.table.client.FixedWidthGrid;
import com.google.gwt.gen2.table.client.ScrollTable;
import com.google.gwt.gen2.table.client.SelectionGrid.SelectionPolicy;
import com.google.gwt.gen2.table.event.client.RowSelectionEvent;
import com.google.gwt.gen2.table.event.client.RowSelectionHandler;
import com.google.gwt.gen2.table.event.client.TableEvent.Row;
import com.google.gwt.gen2.table.override.client.FlexTable.FlexCellFormatter;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.InlineLabel;
import com.google.gwt.user.client.ui.LayoutPanel;
import com.google.gwt.user.client.ui.Tree;
import com.google.gwt.user.client.ui.TreeItem;
import com.google.gwt.user.client.ui.Widget;

import edu.ycp.cs.netcoder.client.status.ProblemDescriptionWidget;
import edu.ycp.cs.netcoder.client.status.StatusAndButtonBarWidget;
import edu.ycp.cs.netcoder.client.status.StatusMessageWidget;
import edu.ycp.cs.netcoder.shared.problems.Course;
import edu.ycp.cs.netcoder.shared.problems.Problem;
import edu.ycp.cs.netcoder.shared.problems.TermAndYear;
import edu.ycp.cs.netcoder.shared.util.Publisher;
import edu.ycp.cs.netcoder.shared.util.Subscriber;

/**
 * View for browsing courses and problems.
 * TODO: refactor out some widgets out of this class (course tree, problem list, etc.)
 */
public class CourseAndProblemView extends NetCoderView implements Subscriber {
    // Tree for displaying user's courses (organized by term/year)
    private Tree courseTree;

    // Table for displaying available problems for selected course
    private FixedWidthFlexTable headerTable;
    private FixedWidthGrid grid;
    private ScrollTable table;

    private StatusAndButtonBarWidget statusAndButtonBar;
    private ProblemDescriptionWidget problemDescriptionWidget;

    private GetCoursesAndProblemsServiceAsync getCoursesAndProblemsService = GWT
            .create(GetCoursesAndProblemsService.class);

    /**
     * Object to manage current Course selection.
     */
    private static class CourseSelection extends Publisher {
        /**
         * Event types broadcast to subscribers.
         */
        public enum Event {
            /** A course was selected. */
            COURSE_SELECTED,

            /** A course was loaded, and its problems are available. */
            COURSE_LOADED,
        }

        private Course current; // course currently loading (most recent selection)
        private Problem[] problemList;

        /**
         * Called to select a course.
         * Initiates loading of course.
         * 
         * @param course the selected Course
         */
        public void courseSelected(Course course) {
            current = course;
            notifySubscribers(Event.COURSE_SELECTED, course);
        }

        /**
         * Called to indicate that a course has been loaded
         * (along with its problems).
         * 
         * @param course       the loaded Course
         * @param problemList  the Course's Problems
         */
        public void courseLoaded(Course course, Problem[] problemList) {
            // Only update the current course if it is the one most
            // recently selected.
            if (course == this.current) {
                this.current = null;
                this.problemList = problemList;
                notifySubscribers(Event.COURSE_LOADED, course);
            }
        }

        /**
         * Get list of Problems for the currently-loaded course.
         * 
         * @return list of Problems
         */
        public Problem[] getProblemList() {
            return problemList;
        }
    }

    /**
     * Constructor.
     * 
     * @param session the Session
     */
    public CourseAndProblemView(Session session) {
        super(session);

        // Subscribe to session ADDED_OBJECT events (to find out when course list is loaded)
        getSession().subscribe(Session.Event.ADDED_OBJECT, this, getSubscriptionRegistrar());

        LayoutPanel layoutPanel = getLayoutPanel();

        // Add grid to display problems
        headerTable = new FixedWidthFlexTable();
        FlexCellFormatter formatter = headerTable.getFlexCellFormatter();
        headerTable.setHTML(0, 0, "Problem name");
        headerTable.setHTML(0, 1, "Description");
        formatter.setColSpan(0, 0, 1);
        formatter.setColSpan(0, 1, 1);

        grid = new FixedWidthGrid();
        grid.setSelectionPolicy(SelectionPolicy.ONE_ROW);
        grid.setSelectionEnabled(true);
        grid.addRowSelectionHandler(new RowSelectionHandler() {
            @Override
            public void onRowSelection(RowSelectionEvent event) {
                //Window.alert("Row selection event!");
                CourseSelection courseSelection = getSession().get(CourseSelection.class);
                Problem[] problemList = courseSelection.getProblemList();
                Set<Row> selectedRows = event.getSelectedRows();
                //Window.alert(selectedRows.size() + " rows selected");
                if (selectedRows.size() == 1) {
                    // A problem has been selected
                    Row row = selectedRows.iterator().next();
                    int index = row.getRowIndex();
                    Problem problem = problemList[index];
                    //Window.alert("Setting problem " + problem.getBriefDescription() + " in session");
                    getSession().add(problem);
                }
            }
        });

        setColumnWidth(0, 100);
        setColumnWidth(1, 300);

        table = new ScrollTable(grid, headerTable);

        layoutPanel.add(table);

        // Status and button bar widget
        this.statusAndButtonBar = new StatusAndButtonBarWidget(getSession(), getSubscriptionRegistrar());
        statusAndButtonBar.addToLeftPanel(new StatusMessageWidget(getSession(), getSubscriptionRegistrar()));
        Button selectProblemButton = new Button("Select problem");
        selectProblemButton.addClickHandler(new ClickHandler() {
            @Override
            public void onClick(ClickEvent event) {
                Problem problem = getSession().get(Problem.class);
                if (problem != null) {
                    // Woot!!!
                    //Window.alert("firing PROBLEM_CHOSEN for " + problem.getBriefDescription());
                    getSession().notifySubscribers(Session.Event.PROBLEM_CHOSEN, problem);
                }
            }
        });
        statusAndButtonBar.addToRightPanel(selectProblemButton);
        layoutPanel.add(statusAndButtonBar);

        // Problem description
        this.problemDescriptionWidget = new ProblemDescriptionWidget(getSession(), getSubscriptionRegistrar());
        layoutPanel.add(problemDescriptionWidget);

        initWidget(layoutPanel);

        // Subscribe to window resize events
        getSession().get(WindowResizeNotifier.class).subscribe(WindowResizeNotifier.WINDOW_RESIZED, this,
                getSubscriptionRegistrar());

        // Subscribe to changes in selected course
        CourseSelection courseSelection = new CourseSelection();
        addSessionObject(courseSelection);
        courseSelection.subscribe(CourseSelection.Event.COURSE_SELECTED, this, getSubscriptionRegistrar());
        courseSelection.subscribe(CourseSelection.Event.COURSE_LOADED, this, getSubscriptionRegistrar());
    }

    private void setColumnWidth(int col, int width) {
        headerTable.setColumnWidth(col, width);
        grid.setColumnWidth(col, width);
    }

    private static class TermAndYearNode extends InlineLabel {
        public TermAndYearNode(TermAndYear termAndYear) {
            super(termAndYear.toString());
        }
    }

    private static class CourseNode extends InlineLabel {
        private Course course;

        public CourseNode(Course course) {
            super(course.toString());
            this.course = course;
        }

        public Course getCourse() {
            return course;
        }
    }

    @Override
    public void eventOccurred(Object key, Publisher publisher, Object hint) {
        if (key == Session.Event.ADDED_OBJECT && hint instanceof Course[]) {
            Course[] courseList = (Course[]) hint;

            // Build the course tree
            courseTree = new Tree();
            TreeItem curTermAndYearTreeItem = null;
            TermAndYear curTermAndYear = null;
            for (Course course : courseList) {
                TermAndYear courseTermAndYear = new TermAndYear(course.getTerm(), course.getYear());
                if (curTermAndYear == null || !curTermAndYear.equals(courseTermAndYear)) {
                    curTermAndYearTreeItem = new TreeItem(new TermAndYearNode(courseTermAndYear));
                    courseTree.addItem(curTermAndYearTreeItem);
                    curTermAndYear = courseTermAndYear;
                }
                curTermAndYearTreeItem.addItem(new CourseNode(course));
            }

            // Handle selection events
            courseTree.addSelectionHandler(new SelectionHandler<TreeItem>() {
                @Override
                public void onSelection(SelectionEvent<TreeItem> event) {
                    TreeItem treeItem = event.getSelectedItem();
                    Widget w = treeItem.getWidget();
                    if (w instanceof CourseNode) {
                        getSession().get(CourseSelection.class).courseSelected(((CourseNode) w).getCourse());
                    }
                }
            });

            getLayoutPanel().add(courseTree);

            doResize();
        } else if (key == WindowResizeNotifier.WINDOW_RESIZED) {
            doResize();
        } else if (key == CourseSelection.Event.COURSE_SELECTED) {
            final Course selectedCourse = (Course) hint;
            AsyncCallback<Problem[]> callback = new AsyncCallback<Problem[]>() {
                @Override
                public void onFailure(Throwable caught) {
                    GWT.log("Could not load problems for course", caught);
                }

                @Override
                public void onSuccess(Problem[] result) {
                    getSession().get(CourseSelection.class).courseLoaded(selectedCourse, result);
                }
            };

            getCoursesAndProblemsService.getProblems(selectedCourse, callback);
        } else if (key == CourseSelection.Event.COURSE_LOADED) {
            showProblems(getSession().get(CourseSelection.class).getProblemList());
        }
    }

    private void showProblems(Problem[] problemList) {
        grid.clear();
        grid.resize(problemList.length, 2);
        int row = 0;
        for (Problem problem : problemList) {
            grid.setWidget(row, 0, new InlineLabel(problem.getTestName()));
            grid.setWidget(row, 1, new InlineLabel(problem.getBriefDescription()));
            row++;
        }
    }

    @Override
    public void unsubscribeFromAll() {
        getSession().get(WindowResizeNotifier.class).unsubscribeFromAll(this);
        getSession().get(CourseSelection.class).unsubscribeFromAll(this);
        getSession().unsubscribeFromAll(this);
    }

    @Override
    public void activate() {
        AsyncCallback<Course[]> callback = new AsyncCallback<Course[]>() {
            @Override
            public void onFailure(Throwable caught) {
                GWT.log("Could not get courses", caught);
            }

            @Override
            public void onSuccess(Course[] result) {
                addSessionObject(result);
            }
        };

        getCoursesAndProblemsService.getCourses(callback);

        doResize(); // size the problems table
    }

    @Override
    public void deactivate() {
        getSubscriptionRegistrar().unsubscribeAllEventSubscribers();

        removeAllSessionObjects();
    }

    private void doResize() {

        getLayoutPanel().setWidgetTopHeight(problemDescriptionWidget, LayoutConstants.TOP_BAR_HEIGHT_PX, Unit.PX,
                LayoutConstants.CP_PROBLEM_DESC_HEIGHT_PX, Unit.PX);

        int availHeight = Window.getClientHeight() - LayoutConstants.TOP_BAR_HEIGHT_PX
                - LayoutConstants.CP_STATUS_AND_BUTTON_BAR_HEIGHT_PX - LayoutConstants.CP_PROBLEM_DESC_HEIGHT_PX;

        if (courseTree != null) {
            getLayoutPanel().setWidgetLeftWidth(courseTree, 0, Unit.PX, LayoutConstants.CP_COURSE_TREE_WIDTH_PX,
                    Unit.PX);
            getLayoutPanel().setWidgetTopHeight(courseTree,
                    LayoutConstants.TOP_BAR_HEIGHT_PX + LayoutConstants.CP_PROBLEM_DESC_HEIGHT_PX, Unit.PX,
                    availHeight, Unit.PX);
        }

        getLayoutPanel().setWidgetRightWidth(table, 0, Unit.PX,
                Window.getClientWidth() - LayoutConstants.CP_COURSE_TREE_WIDTH_PX - 8, Unit.PX);
        getLayoutPanel().setWidgetTopHeight(table,
                LayoutConstants.TOP_BAR_HEIGHT_PX + LayoutConstants.CP_PROBLEM_DESC_HEIGHT_PX, Unit.PX, availHeight,
                Unit.PX);

        getLayoutPanel().setWidgetBottomHeight(statusAndButtonBar, 0, Unit.PX,
                LayoutConstants.CP_STATUS_AND_BUTTON_BAR_HEIGHT_PX, Unit.PX);
    }
}