org.unitime.timetable.gwt.client.sectioning.SectioningStatusPage.java Source code

Java tutorial

Introduction

Here is the source code for org.unitime.timetable.gwt.client.sectioning.SectioningStatusPage.java

Source

/*
 * Licensed to The Apereo Foundation under one or more contributor license
 * agreements. See the NOTICE file distributed with this work for
 * additional information regarding copyright ownership.
 *
 * The Apereo Foundation licenses this file to you 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 org.unitime.timetable.gwt.client.sectioning;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import org.unitime.timetable.gwt.client.ToolBox;
import org.unitime.timetable.gwt.client.page.UniTimeNotifications;
import org.unitime.timetable.gwt.client.sectioning.EnrollmentTable.TopCell;
import org.unitime.timetable.gwt.client.sectioning.SectioningStatusFilterBox.SectioningStatusFilterRpcRequest;
import org.unitime.timetable.gwt.client.widgets.LoadingWidget;
import org.unitime.timetable.gwt.client.widgets.SimpleForm;
import org.unitime.timetable.gwt.client.widgets.UniTimeDialogBox;
import org.unitime.timetable.gwt.client.widgets.UniTimeHeaderPanel;
import org.unitime.timetable.gwt.client.widgets.UniTimeTabPanel;
import org.unitime.timetable.gwt.client.widgets.UniTimeTable;
import org.unitime.timetable.gwt.client.widgets.UniTimeWidget;
import org.unitime.timetable.gwt.client.widgets.UniTimeTable.HasColSpan;
import org.unitime.timetable.gwt.client.widgets.UniTimeTable.MouseClickListener;
import org.unitime.timetable.gwt.client.widgets.UniTimeTable.TableEvent;
import org.unitime.timetable.gwt.client.widgets.UniTimeTableHeader;
import org.unitime.timetable.gwt.client.widgets.UniTimeTableHeader.Operation;
import org.unitime.timetable.gwt.client.widgets.UniTimeTextBox;
import org.unitime.timetable.gwt.client.widgets.UniTimeTable.HasCellAlignment;
import org.unitime.timetable.gwt.resources.GwtConstants;
import org.unitime.timetable.gwt.resources.StudentSectioningConstants;
import org.unitime.timetable.gwt.resources.StudentSectioningMessages;
import org.unitime.timetable.gwt.resources.StudentSectioningResources;
import org.unitime.timetable.gwt.services.SectioningService;
import org.unitime.timetable.gwt.services.SectioningServiceAsync;
import org.unitime.timetable.gwt.shared.ClassAssignmentInterface;
import org.unitime.timetable.gwt.shared.ClassAssignmentInterface.Enrollment;
import org.unitime.timetable.gwt.shared.ClassAssignmentInterface.EnrollmentInfo;
import org.unitime.timetable.gwt.shared.ClassAssignmentInterface.SectioningAction;
import org.unitime.timetable.gwt.shared.ClassAssignmentInterface.StudentInfo;
import org.unitime.timetable.gwt.shared.OnlineSectioningInterface.SectioningProperties;

import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Style.Overflow;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyUpEvent;
import com.google.gwt.event.dom.client.KeyUpHandler;
import com.google.gwt.event.logical.shared.CloseEvent;
import com.google.gwt.event.logical.shared.CloseHandler;
import com.google.gwt.event.logical.shared.OpenEvent;
import com.google.gwt.event.logical.shared.OpenHandler;
import com.google.gwt.event.logical.shared.SelectionEvent;
import com.google.gwt.event.logical.shared.SelectionHandler;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.http.client.URL;
import com.google.gwt.i18n.client.DateTimeFormat;
import com.google.gwt.i18n.client.NumberFormat;
import com.google.gwt.user.client.History;
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.CheckBox;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FocusPanel;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HasHorizontalAlignment;
import com.google.gwt.user.client.ui.HasVerticalAlignment;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.PopupPanel;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.ScrollPanel;
import com.google.gwt.user.client.ui.TextArea;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.SuggestOracle.Callback;
import com.google.gwt.user.client.ui.SuggestOracle.Request;
import com.google.gwt.user.client.ui.SuggestOracle.Response;
import com.google.gwt.user.client.ui.SuggestOracle.Suggestion;
import com.google.gwt.user.client.ui.Widget;

/**
 * @author Tomas Muller
 */
public class SectioningStatusPage extends Composite {
    public static final StudentSectioningMessages MESSAGES = GWT.create(StudentSectioningMessages.class);
    public static final StudentSectioningResources RESOURCES = GWT.create(StudentSectioningResources.class);
    public static final StudentSectioningConstants CONSTANTS = GWT.create(StudentSectioningConstants.class);
    public static final GwtConstants GWT_CONSTANTS = GWT.create(GwtConstants.class);
    private static DateTimeFormat sDF = DateTimeFormat.getFormat(CONSTANTS.requestDateFormat());
    private static DateTimeFormat sTSF = DateTimeFormat.getFormat(CONSTANTS.timeStampFormat());

    private final SectioningServiceAsync iSectioningService = GWT.create(SectioningService.class);

    private SectioningStatusFilterBox iFilter;

    private Button iSearch = null;
    private Image iLoadingImage = null;

    private VerticalPanel iSectioningPanel = null;

    private VerticalPanel iPanel = null;
    private HorizontalPanel iFilterPanel = null;

    private UniTimeTable<EnrollmentInfo> iCourseTable = null;
    private UniTimeTable<StudentInfo> iStudentTable = null;
    private UniTimeTable<SectioningAction> iLogTable = null;

    private UniTimeDialogBox iEnrollmentDialog = null;
    private EnrollmentTable iEnrollmentTable = null;
    private ScrollPanel iEnrollmentScroll = null;
    private UniTimeTabPanel iTabPanel = null;
    private int iTabIndex = 0;
    private FocusPanel iTabPanelWithFocus = null;
    private SectioningProperties iProperties = null;

    private HTML iError = null, iCourseTableHint, iStudentTableHint;
    private String iLastFilterOnEnter = null, iCourseFilter = null;
    private SectioningStatusFilterRpcRequest iCourseFilterRequest = null;
    private Map<String, String> iStates = null;
    private int iStatusColumn = 0;
    private UniTimeTextBox iSubject, iCC;
    private UniTimeWidget<ListBox> iStatus;
    private TextArea iMessage;
    private Set<Long> iSelectedStudentIds = new HashSet<Long>();
    private boolean iOnline;

    public SectioningStatusPage(boolean online) {
        iOnline = online;

        iPanel = new VerticalPanel();
        iSectioningPanel = new VerticalPanel();

        iFilterPanel = new HorizontalPanel();
        iFilterPanel.setSpacing(3);

        Label filterLabel = new Label(MESSAGES.filter());
        iFilterPanel.add(filterLabel);
        iFilterPanel.setCellVerticalAlignment(filterLabel, HasVerticalAlignment.ALIGN_MIDDLE);

        iFilter = new SectioningStatusFilterBox(online);
        iFilterPanel.add(iFilter);

        iSearch = new Button(MESSAGES.buttonSearch());
        iSearch.setAccessKey('s');
        iSearch.addStyleName("unitime-NoPrint");
        iFilterPanel.add(iSearch);
        iFilterPanel.setCellVerticalAlignment(iSearch, HasVerticalAlignment.ALIGN_TOP);

        iLoadingImage = new Image(RESOURCES.loading_small());
        iLoadingImage.setVisible(false);
        iFilterPanel.add(iLoadingImage);
        iFilterPanel.setCellVerticalAlignment(iLoadingImage, HasVerticalAlignment.ALIGN_MIDDLE);

        iSectioningPanel.add(iFilterPanel);
        iSectioningPanel.setCellHorizontalAlignment(iFilterPanel, HasHorizontalAlignment.ALIGN_CENTER);

        iCourseTable = new UniTimeTable<EnrollmentInfo>();
        iStudentTable = new UniTimeTable<StudentInfo>();
        iLogTable = new UniTimeTable<SectioningAction>();

        VerticalPanel courseTableWithHint = new VerticalPanel();
        courseTableWithHint.add(iCourseTable);
        iCourseTableHint = new HTML(MESSAGES.sectioningStatusReservationHint());
        iCourseTableHint.setStyleName("unitime-Hint");
        courseTableWithHint.add(iCourseTableHint);
        courseTableWithHint.setCellHorizontalAlignment(iCourseTableHint, HasHorizontalAlignment.ALIGN_RIGHT);

        VerticalPanel studentTableWithHint = new VerticalPanel();
        studentTableWithHint.add(iStudentTable);
        iStudentTableHint = new HTML(MESSAGES.sectioningStatusPriorityHint());
        iStudentTableHint.setStyleName("unitime-Hint");
        studentTableWithHint.add(iStudentTableHint);
        studentTableWithHint.setCellHorizontalAlignment(iStudentTableHint, HasHorizontalAlignment.ALIGN_RIGHT);

        iTabPanel = new UniTimeTabPanel();
        iTabPanel.add(courseTableWithHint, MESSAGES.tabEnrollments(), true);
        iTabPanel.selectTab(0);
        iTabPanel.add(studentTableWithHint, MESSAGES.tabStudents(), true);
        iTabPanel.setVisible(false);

        iTabPanel.addSelectionHandler(new SelectionHandler<Integer>() {
            @Override
            public void onSelection(SelectionEvent<Integer> event) {
                iTabIndex = event.getSelectedItem();
                loadDataIfNeeded();
            }
        });

        iTabPanelWithFocus = new FocusPanel(iTabPanel);
        iTabPanelWithFocus.setStyleName("unitime-FocusPanel");
        iSectioningPanel.add(iTabPanelWithFocus);

        iTabPanelWithFocus.addKeyUpHandler(new KeyUpHandler() {
            public void onKeyUp(KeyUpEvent event) {
                if (event.getNativeEvent().getCtrlKey()
                        && (event.getNativeKeyCode() == 'e' || event.getNativeKeyCode() == 'E')) {
                    iTabPanel.selectTab(0);
                    event.preventDefault();
                }
                if (event.getNativeEvent().getCtrlKey()
                        && (event.getNativeKeyCode() == 's' || event.getNativeKeyCode() == 'S')) {
                    iTabPanel.selectTab(1);
                    event.preventDefault();
                }
                if (event.getNativeEvent().getCtrlKey()
                        && (event.getNativeKeyCode() == 'l' || event.getNativeKeyCode() == 'L')) {
                    iTabPanel.selectTab(2);
                    event.preventDefault();
                }
            }
        });

        iSectioningPanel.setWidth("100%");

        iPanel.add(iSectioningPanel);

        iError = new HTML();
        iError.setStyleName("unitime-ErrorMessage");
        iError.setVisible(false);
        iPanel.add(iError);
        iPanel.setCellHorizontalAlignment(iError, HasHorizontalAlignment.ALIGN_CENTER);

        initWidget(iPanel);

        iSearch.addClickHandler(new ClickHandler() {
            public void onClick(ClickEvent event) {
                loadData();
            }
        });

        iFilter.addKeyUpHandler(new KeyUpHandler() {
            public void onKeyUp(KeyUpEvent event) {
                if (event.getNativeKeyCode() == KeyCodes.KEY_ENTER) {
                    if (iFilter.getValue().equals(iLastFilterOnEnter) && !iFilter.getValue().equals(iCourseFilter))
                        loadData();
                    else
                        iLastFilterOnEnter = iFilter.getValue();
                }
            }
        });

        iCourseTable.addMouseClickListener(new MouseClickListener<ClassAssignmentInterface.EnrollmentInfo>() {
            @Override
            public void onMouseClick(final TableEvent<EnrollmentInfo> event) {
                if (event.getData() == null || event.getData().getCourseId() == null)
                    return; // header or footer
                iCourseTable.clearHover();
                setLoading(true);
                final Long id = (event.getData().getConfigId() == null ? event.getData().getOfferingId()
                        : -event.getData().getClazzId());
                iError.setVisible(false);
                if (event.getData().getConfigId() == null)
                    LoadingWidget.getInstance().show(MESSAGES.loadingEnrollments(
                            MESSAGES.course(event.getData().getSubject(), event.getData().getCourseNbr())));
                else
                    LoadingWidget.getInstance()
                            .show(MESSAGES.loadingEnrollments(
                                    MESSAGES.clazz(event.getData().getSubject(), event.getData().getCourseNbr(),
                                            event.getData().getSubpart(), event.getData().getClazz())));
                if (iOnline) {
                    iSectioningService.canApprove(id, new AsyncCallback<List<Long>>() {
                        @Override
                        public void onSuccess(final List<Long> courseIdsCanApprove) {
                            iSectioningService.findEnrollments(iOnline, iCourseFilter, iCourseFilterRequest,
                                    event.getData().getCourseId(), event.getData().getClazzId(),
                                    new AsyncCallback<List<Enrollment>>() {
                                        @Override
                                        public void onFailure(Throwable caught) {
                                            LoadingWidget.getInstance().hide();
                                            UniTimeNotifications.error(caught);
                                            setLoading(false);
                                            iError.setHTML(caught.getMessage());
                                            iError.setVisible(true);
                                            ToolBox.checkAccess(caught);
                                        }

                                        @Override
                                        public void onSuccess(List<Enrollment> result) {
                                            LoadingWidget.getInstance().hide();
                                            setLoading(false);
                                            iEnrollmentTable.clear();
                                            iEnrollmentTable.setId(id);
                                            iEnrollmentTable.populate(result, courseIdsCanApprove);
                                            if (event.getData().getConfigId() == null)
                                                iEnrollmentDialog.setText(MESSAGES.titleEnrollments(
                                                        MESSAGES.course(event.getData().getSubject(),
                                                                event.getData().getCourseNbr())));
                                            else
                                                iEnrollmentDialog.setText(MESSAGES.titleEnrollments(MESSAGES.clazz(
                                                        event.getData().getSubject(),
                                                        event.getData().getCourseNbr(),
                                                        event.getData().getSubpart(), event.getData().getClazz())));
                                            iEnrollmentDialog.center();
                                        }
                                    });
                        }

                        @Override
                        public void onFailure(Throwable caught) {
                            LoadingWidget.getInstance().hide();
                            UniTimeNotifications.error(caught);
                            setLoading(false);
                            iError.setHTML(caught.getMessage());
                            iError.setVisible(true);
                            ToolBox.checkAccess(caught);
                        }
                    });
                } else {
                    iSectioningService.findEnrollments(iOnline, iCourseFilter, iCourseFilterRequest,
                            event.getData().getCourseId(), event.getData().getClazzId(),
                            new AsyncCallback<List<Enrollment>>() {
                                @Override
                                public void onFailure(Throwable caught) {
                                    LoadingWidget.getInstance().hide();
                                    UniTimeNotifications.error(caught);
                                    setLoading(false);
                                    iError.setHTML(caught.getMessage());
                                    iError.setVisible(true);
                                    ToolBox.checkAccess(caught);
                                }

                                @Override
                                public void onSuccess(List<Enrollment> result) {
                                    LoadingWidget.getInstance().hide();
                                    setLoading(false);
                                    iEnrollmentTable.clear();
                                    iEnrollmentTable.setId(id);
                                    iEnrollmentTable.populate(result, null);
                                    if (event.getData().getConfigId() == null)
                                        iEnrollmentDialog.setText(MESSAGES.titleEnrollments(MESSAGES.course(
                                                event.getData().getSubject(), event.getData().getCourseNbr())));
                                    else
                                        iEnrollmentDialog.setText(MESSAGES.titleEnrollments(MESSAGES.clazz(
                                                event.getData().getSubject(), event.getData().getCourseNbr(),
                                                event.getData().getSubpart(), event.getData().getClazz())));
                                    iEnrollmentDialog.center();
                                }
                            });
                }
            }
        });

        iStudentTable.addMouseClickListener(new MouseClickListener<StudentInfo>() {
            @Override
            public void onMouseClick(final TableEvent<StudentInfo> event) {
                if (event.getData() == null || event.getData().getStudent() == null)
                    return; // header or footer
                iStudentTable.clearHover();
                LoadingWidget.getInstance()
                        .show(MESSAGES.loadingEnrollment(event.getData().getStudent().getName()));
                iError.setVisible(false);
                iEnrollmentTable.showStudentSchedule(event.getData().getStudent(), new AsyncCallback<Boolean>() {
                    @Override
                    public void onFailure(Throwable caught) {
                        LoadingWidget.getInstance().hide();
                        iError.setHTML(caught.getMessage());
                        iError.setVisible(true);
                    }

                    @Override
                    public void onSuccess(Boolean result) {
                        LoadingWidget.getInstance().hide();
                    }
                });
            }
        });

        iLogTable.addMouseClickListener(new MouseClickListener<ClassAssignmentInterface.SectioningAction>() {
            @Override
            public void onMouseClick(TableEvent<SectioningAction> event) {
                if (event.getData() != null && event.getData().getProto() != null) {
                    final HTML widget = new HTML(event.getData().getProto());
                    final ScrollPanel scroll = new ScrollPanel(widget);
                    scroll.setHeight(((int) (0.8 * Window.getClientHeight())) + "px");
                    scroll.setStyleName("unitime-ScrollPanel");
                    final UniTimeDialogBox dialog = new UniTimeDialogBox(true, false);
                    dialog.setWidget(scroll);
                    dialog.setText(MESSAGES.dialogChangeMessage(event.getData().getStudent().getName()));
                    dialog.setEscapeToHide(true);
                    dialog.addOpenHandler(new OpenHandler<UniTimeDialogBox>() {
                        @Override
                        public void onOpen(OpenEvent<UniTimeDialogBox> event) {
                            RootPanel.getBodyElement().getStyle().setOverflow(Overflow.HIDDEN);
                            scroll.setHeight(Math.min(widget.getElement().getScrollHeight(),
                                    Window.getClientHeight() * 80 / 100) + "px");
                            dialog.setPopupPosition(
                                    Math.max(Window.getScrollLeft()
                                            + (Window.getClientWidth() - dialog.getOffsetWidth()) / 2, 0),
                                    Math.max(
                                            Window.getScrollTop()
                                                    + (Window.getClientHeight() - dialog.getOffsetHeight()) / 2,
                                            0));
                        }
                    });
                    dialog.addCloseHandler(new CloseHandler<PopupPanel>() {
                        @Override
                        public void onClose(CloseEvent<PopupPanel> event) {
                            iLogTable.clearHover();
                            RootPanel.getBodyElement().getStyle().setOverflow(Overflow.AUTO);
                        }
                    });
                    dialog.center();
                }
            }
        });

        History.addValueChangeHandler(new ValueChangeHandler<String>() {
            @Override
            public void onValueChange(ValueChangeEvent<String> event) {
                iCourseTable.clearTable();
                iStudentTable.clearTable();
                iLogTable.clearTable();
                if (event.getValue().endsWith("@")) {
                    iFilter.setValue(event.getValue().substring(0, event.getValue().length() - 1));
                    iTabPanel.selectTab(1);
                } else if (event.getValue().endsWith("$")) {
                    iFilter.setValue(event.getValue().substring(0, event.getValue().length() - 1));
                    iTabPanel.selectTab(2);
                } else {
                    iFilter.setValue(event.getValue());
                    if (iTabIndex != 0)
                        iTabPanel.selectTab(0);
                    else
                        loadData();
                }
            }
        });

        iEnrollmentTable = new EnrollmentTable(false, iOnline);
        iEnrollmentScroll = new ScrollPanel(iEnrollmentTable);
        iEnrollmentScroll.setHeight(((int) (0.8 * Window.getClientHeight())) + "px");
        iEnrollmentScroll.setStyleName("unitime-ScrollPanel");
        iEnrollmentDialog = new UniTimeDialogBox(true, false);
        iEnrollmentDialog.setEscapeToHide(true);
        iEnrollmentDialog.setWidget(iEnrollmentScroll);
        iEnrollmentDialog.addOpenHandler(new OpenHandler<UniTimeDialogBox>() {
            @Override
            public void onOpen(OpenEvent<UniTimeDialogBox> event) {
                RootPanel.getBodyElement().getStyle().setOverflow(Overflow.HIDDEN);
                iEnrollmentScroll.setHeight(Math.min(iEnrollmentTable.getElement().getScrollHeight(),
                        Window.getClientHeight() * 80 / 100) + "px");
                iEnrollmentDialog.setPopupPosition(
                        Math.max(Window.getScrollLeft()
                                + (Window.getClientWidth() - iEnrollmentDialog.getOffsetWidth()) / 2, 0),
                        Math.max(Window.getScrollTop()
                                + (Window.getClientHeight() - iEnrollmentDialog.getOffsetHeight()) / 2, 0));
            }
        });
        iEnrollmentTable.getHeader().addButton("close", MESSAGES.buttonClose(), new ClickHandler() {
            @Override
            public void onClick(ClickEvent event) {
                iEnrollmentDialog.hide();
            }
        });

        iEnrollmentDialog.addCloseHandler(new CloseHandler<PopupPanel>() {
            @Override
            public void onClose(CloseEvent<PopupPanel> event) {
                RootPanel.getBodyElement().getStyle().setOverflow(Overflow.AUTO);
            }
        });

        if (iOnline) {
            iSectioningService.getProperties(null, new AsyncCallback<SectioningProperties>() {
                @Override
                public void onSuccess(SectioningProperties result) {
                    iProperties = result;
                    if (iProperties.isChangeLog())
                        iTabPanel.add(iLogTable, MESSAGES.tabChangeLog(), true);
                    checkLastQuery();
                }

                @Override
                public void onFailure(Throwable caught) {
                    iError.setHTML(caught.getMessage());
                    iError.setVisible(true);
                }
            });
        } else {
            checkLastQuery();
        }

        iStatus = new UniTimeWidget<ListBox>(new ListBox());
        iStatus.getWidget().addItem("");
        iStatus.getWidget().setSelectedIndex(0);

        iSectioningService.lookupStudentSectioningStates(new AsyncCallback<Map<String, String>>() {

            @Override
            public void onFailure(Throwable caught) {
            }

            @Override
            public void onSuccess(Map<String, String> result) {
                iStates = result;
                for (final String ref : new TreeSet<String>(iStates.keySet())) {
                    iStatus.getWidget().addItem(iStates.get(ref), ref);
                }
                for (int i = 0; i < iStatus.getWidget().getItemCount(); i++) {
                    if ("Cancelled".equalsIgnoreCase(iStatus.getWidget().getValue(i))) {
                        iStatus.getWidget().setSelectedIndex(i);
                        break;
                    }
                }
            }
        });

        iSubject = new UniTimeTextBox(512, 473);
        iSubject.setText(MESSAGES.defaulSubject());
        iCC = new UniTimeTextBox(512, 473);
        iMessage = new TextArea();
        iMessage.setStyleName("unitime-TextArea");
        iMessage.setVisibleLines(10);
        iMessage.setCharacterWidth(80);

    }

    private void checkLastQuery() {
        if (Window.Location.getParameter("q") != null) {
            iFilter.setValue(Window.Location.getParameter("q"), true);
            if (Window.Location.getParameter("t") != null) {
                if ("2".equals(Window.Location.getParameter("t"))) {
                    iTabPanel.selectTab(1);
                } else {
                    iTabPanel.selectTab(0);
                }
            } else {
                loadData();
            }
        } else if (Window.Location.getHash() != null && !Window.Location.getHash().isEmpty()) {
            String hash = URL.decode(Window.Location.getHash().substring(1));
            if (!hash.matches("^[0-9]+\\:?[0-9]*@?$")) {
                if (hash.endsWith("@")) {
                    iFilter.setValue(hash.substring(0, hash.length() - 1), true);
                    iTabPanel.selectTab(1);
                } else if (hash.endsWith("$")) {
                    iFilter.setValue(hash.substring(0, hash.length() - 1), true);
                    iTabPanel.selectTab(2);
                } else {
                    iFilter.setValue(hash, true);
                    loadData();
                }
            }
        } else {
            String q = SectioningStatusCookie.getInstance().getQuery(iOnline);
            if (q != null)
                iFilter.setValue(q);
            int t = SectioningStatusCookie.getInstance().getTab(iOnline);
            if (t >= 0 && t < iTabPanel.getTabCount()) {
                iTabPanel.selectTab(t, false);
                iTabIndex = -1;
            }
            if (GWT_CONSTANTS.searchWhenPageIsLoaded() && q != null && !q.isEmpty())
                loadData();
        }
    }

    private void setLoading(boolean loading) {
        iLoadingImage.setVisible(loading);
        iSearch.setVisible(!loading);
    }

    private void loadData() {
        iCourseTable.clearTable();
        iStudentTable.clearTable();
        iLogTable.clearTable();
        loadDataIfNeeded();
    }

    private void loadDataIfNeeded() {
        if (iTabIndex < 0) {
            iTabPanel.selectTab(SectioningStatusCookie.getInstance().getTab(iOnline));
            return;
        }

        iCourseFilter = iFilter.getValue();
        iCourseFilterRequest = iFilter.getElementsRequest();
        History.newItem(iCourseFilter + (iTabIndex == 1 ? "@" : iTabIndex == 2 ? "$" : ""), false);
        SectioningStatusCookie.getInstance().setQueryTab(iOnline, iFilter.getValue(), iTabIndex);

        if (iFilter.isFilterPopupShowing())
            iFilter.hideFilterPopup();

        if (iTabIndex == 0 && iCourseTable.getRowCount() > 0)
            return;
        if (iTabIndex == 1 && iStudentTable.getRowCount() > 0)
            return;
        if (iTabIndex == 2 && iLogTable.getRowCount() > 0)
            return;

        LoadingWidget.getInstance().show(MESSAGES.loadingData());
        setLoading(true);
        iError.setVisible(false);
        if (iTabIndex == 0) {
            iSectioningService.findEnrollmentInfos(iOnline, iCourseFilter, iCourseFilterRequest, null,
                    new AsyncCallback<List<EnrollmentInfo>>() {
                        @Override
                        public void onFailure(Throwable caught) {
                            LoadingWidget.getInstance().hide();
                            setLoading(false);
                            iError.setHTML(caught.getMessage());
                            iError.setVisible(true);
                            iTabPanel.setVisible(false);
                            ToolBox.checkAccess(caught);
                        }

                        @Override
                        public void onSuccess(List<EnrollmentInfo> result) {
                            if (result.isEmpty()) {
                                iError.setHTML(MESSAGES.exceptionNoMatchingResultsFound(iCourseFilter));
                                iError.setVisible(true);
                                iTabPanel.setVisible(false);
                            } else {
                                populateCourseTable(result);
                                iTabPanel.setVisible(true);
                            }
                            setLoading(false);
                            LoadingWidget.getInstance().hide();
                        }
                    });
        } else if (iTabIndex == 1) {
            if (iOnline) {
                iSectioningService.findStudentInfos(iOnline, iCourseFilter, iCourseFilterRequest,
                        new AsyncCallback<List<StudentInfo>>() {
                            @Override
                            public void onFailure(Throwable caught) {
                                LoadingWidget.getInstance().hide();
                                setLoading(false);
                                iError.setHTML(caught.getMessage());
                                iError.setVisible(true);
                                iTabPanel.setVisible(false);
                                ToolBox.checkAccess(caught);
                            }

                            @Override
                            public void onSuccess(List<StudentInfo> result) {
                                if (result.isEmpty()) {
                                    iError.setHTML(MESSAGES.exceptionNoMatchingResultsFound(iCourseFilter));
                                    iError.setVisible(true);
                                    iTabPanel.setVisible(false);
                                } else {
                                    populateStudentTable(result);
                                    iTabPanel.setVisible(true);
                                }
                                setLoading(false);
                                LoadingWidget.getInstance().hide();
                            }
                        });
            } else {
                iSectioningService.findStudentInfos(iOnline, iCourseFilter, iCourseFilterRequest,
                        new AsyncCallback<List<StudentInfo>>() {
                            @Override
                            public void onFailure(Throwable caught) {
                                LoadingWidget.getInstance().hide();
                                setLoading(false);
                                iError.setHTML(caught.getMessage());
                                iError.setVisible(true);
                                iTabPanel.setVisible(false);
                                ToolBox.checkAccess(caught);
                            }

                            @Override
                            public void onSuccess(List<StudentInfo> result) {
                                if (result.isEmpty()) {
                                    iError.setHTML(MESSAGES.exceptionNoMatchingResultsFound(iCourseFilter));
                                    iError.setVisible(true);
                                    iTabPanel.setVisible(false);
                                } else {
                                    populateStudentTable(result);
                                    iTabPanel.setVisible(true);
                                }
                                setLoading(false);
                                LoadingWidget.getInstance().hide();
                            }
                        });
            }
        } else if (iOnline) {
            iSectioningService.changeLog(iCourseFilter, new AsyncCallback<List<SectioningAction>>() {
                @Override
                public void onFailure(Throwable caught) {
                    LoadingWidget.getInstance().hide();
                    setLoading(false);
                    iError.setHTML(caught.getMessage());
                    iError.setVisible(true);
                    iTabPanel.setVisible(false);
                    ToolBox.checkAccess(caught);
                }

                @Override
                public void onSuccess(final List<SectioningAction> result) {
                    populateChangeLog(result);
                    iTabPanel.setVisible(true);
                    setLoading(false);
                    LoadingWidget.getInstance().hide();
                }
            });
        }
    }

    private List<Widget> line(final EnrollmentInfo e) {
        List<Widget> line = new ArrayList<Widget>();
        if (e.getConfigId() == null) {
            if (e.getCourseId() != null) {
                final Image showDetails = new Image(RESOURCES.treeClosed());
                showDetails.addClickHandler(new ClickHandler() {
                    @Override
                    public void onClick(ClickEvent event) {
                        final int row = iCourseTable.getCellForEvent(event).getRowIndex();
                        if (row + 1 == iCourseTable.getRowCount()
                                || iCourseTable.getData(row + 1).getConfigId() == null) { // open
                            setLoading(true);
                            iError.setVisible(false);
                            showDetails.setResource(RESOURCES.treeOpen());
                            iSectioningService.findEnrollmentInfos(iOnline, iCourseFilter, iCourseFilterRequest,
                                    e.getCourseId(), new AsyncCallback<List<EnrollmentInfo>>() {
                                        @Override
                                        public void onFailure(Throwable caught) {
                                            setLoading(false);
                                            iError.setHTML(caught.getMessage());
                                            iError.setVisible(true);
                                            ToolBox.checkAccess(caught);
                                        }

                                        @Override
                                        public void onSuccess(List<EnrollmentInfo> result) {
                                            setLoading(false);
                                            int r = row + 1;
                                            for (EnrollmentInfo e : result) {
                                                iCourseTable.insertRow(r);
                                                iCourseTable.setRow(r, e, line(e));
                                                r++;
                                            }
                                        }
                                    });
                        } else {
                            for (int r = row + 1; r < iCourseTable.getRowCount(); r++) {
                                if (iCourseTable.getData(r).getConfigId() == null)
                                    break;
                                iCourseTable.getRowFormatter().setVisible(r,
                                        !iCourseTable.getRowFormatter().isVisible(r));
                            }
                            showDetails.setResource(RESOURCES.treeClosed());
                        }
                        event.getNativeEvent().stopPropagation();
                        event.getNativeEvent().preventDefault();
                    }
                });
                line.add(showDetails);
            } else {
                line.add(new Label());
            }
            line.add(new Label(e.getSubject(), false));
            line.add(new Label(e.getCourseNbr(), false));
            line.add(new TitleCell(e.getTitle() == null ? "" : e.getTitle()));
            line.add(new Label(e.getConsent() == null ? "" : e.getConsent(), false));
        } else {
            line.add(new Label());
            line.add(new HTML("&nbsp;&nbsp;" + (e.getSubpart() == null ? "" : e.getIndent() + e.getSubpart()),
                    false));
            line.add(new HTML(e.getClazz() == null ? "" : e.getIndent() + e.getClazz(), false));
            line.add(new Label(e.getAssignment().getDays().isEmpty() ? ""
                    : e.getAssignment().getDaysString(CONSTANTS.shortDays()) + " "
                            + e.getAssignment().getStartString(CONSTANTS.useAmPm()) + " - "
                            + e.getAssignment().getEndString(CONSTANTS.useAmPm()),
                    false));
            line.add(new Label(!e.getAssignment().hasDatePattern() ? "" : e.getAssignment().getDatePattern(),
                    false));
            line.add(new Label(!e.getAssignment().hasRoom() ? "" : e.getAssignment().getRooms(","), false));
        }
        if (e.getCourseId() == null)
            line.add(new NumberCell(e.getAvailable(), e.getLimit()));
        else
            line.add(new AvailableCell(e));
        line.add(new NumberCell(null, e.getProjection()));
        line.add(new NumberCell(e.getEnrollment(), e.getTotalEnrollment()));
        line.add(new WaitListCell(e));
        line.add(new NumberCell(e.getReservation(), e.getTotalReservation()));
        line.add(new NumberCell(e.getConsentNeeded(), e.getTotalConsentNeeded()));
        return line;
    }

    public void populateCourseTable(List<EnrollmentInfo> result) {
        List<Widget> header = new ArrayList<Widget>();

        UniTimeTableHeader hOperations = new UniTimeTableHeader("");
        header.add(hOperations);

        UniTimeTableHeader hSubject = new UniTimeTableHeader(
                MESSAGES.colSubject() + "<br>&nbsp;&nbsp;" + MESSAGES.colSubpart());
        header.add(hSubject);
        addSortOperation(hSubject, EnrollmentComparator.SortBy.SUBJECT, MESSAGES.colSubject());

        UniTimeTableHeader hCourse = new UniTimeTableHeader(MESSAGES.colCourse() + "<br>" + MESSAGES.colClass());
        header.add(hCourse);
        addSortOperation(hCourse, EnrollmentComparator.SortBy.COURSE, MESSAGES.colCourse());

        UniTimeTableHeader hTitleSubpart = new UniTimeTableHeader(
                MESSAGES.colTitle() + "<br>" + MESSAGES.colTime());
        header.add(hTitleSubpart);
        addSortOperation(hTitleSubpart, EnrollmentComparator.SortBy.TITLE, MESSAGES.colTitle());

        UniTimeTableHeader hStart = new UniTimeTableHeader("<br>" + MESSAGES.colDate());
        header.add(hStart);

        UniTimeTableHeader hRoom = new UniTimeTableHeader(MESSAGES.colConsent() + "<br>" + MESSAGES.colRoom());
        header.add(hRoom);
        addSortOperation(hRoom, EnrollmentComparator.SortBy.CONSENT, MESSAGES.colConsent());

        UniTimeTableHeader hLimit = new UniTimeTableHeader(MESSAGES.colAvailable());
        header.add(hLimit);
        addSortOperation(hLimit, EnrollmentComparator.SortBy.LIMIT, MESSAGES.colAvailable());

        UniTimeTableHeader hProjection = new UniTimeTableHeader(MESSAGES.colProjection());
        header.add(hProjection);
        addSortOperation(hProjection, EnrollmentComparator.SortBy.PROJECTION, MESSAGES.colProjection());

        UniTimeTableHeader hEnrollment = new UniTimeTableHeader(MESSAGES.colEnrollment());
        header.add(hEnrollment);
        addSortOperation(hEnrollment, EnrollmentComparator.SortBy.ENROLLMENT, MESSAGES.colEnrollment());

        UniTimeTableHeader hWaitListed = new UniTimeTableHeader(MESSAGES.colWaitListed());
        header.add(hWaitListed);
        addSortOperation(hWaitListed, EnrollmentComparator.SortBy.WAITLIST, MESSAGES.colWaitListed());

        UniTimeTableHeader hReserved = new UniTimeTableHeader(MESSAGES.colReserved());
        header.add(hReserved);
        addSortOperation(hReserved, EnrollmentComparator.SortBy.RESERVATION, MESSAGES.colReserved());

        UniTimeTableHeader hConsent = new UniTimeTableHeader(MESSAGES.colNeedConsent());
        header.add(hConsent);
        addSortOperation(hConsent, EnrollmentComparator.SortBy.NEED_CONSENT,
                MESSAGES.colNeedConsent().replace("<br>", " "));

        iCourseTable.addRow(null, header);

        boolean hasReservation = false;
        for (EnrollmentInfo e : result) {
            iCourseTable.addRow(e, line(e));
            if (!hasReservation && AvailableCell.hasReservedSpace(e))
                hasReservation = true;
            iCourseTable.getRowFormatter().getElement(iCourseTable.getRowCount() - 1).getStyle()
                    .setBackgroundColor("#f3f3f3");
        }

        // Total line
        if (iCourseTable.getRowCount() >= 2) {
            for (int c = 0; c < iCourseTable.getCellCount(iCourseTable.getRowCount() - 1); c++)
                iCourseTable.getCellFormatter().setStyleName(iCourseTable.getRowCount() - 1, c, "unitime-TotalRow");
            iCourseTable.getRowFormatter().getElement(iCourseTable.getRowCount() - 1).getStyle()
                    .clearBackgroundColor();
        }

        iCourseTableHint.setVisible(hasReservation);

        if (SectioningStatusCookie.getInstance().getSortBy(iOnline, 0) != 0) {
            boolean asc = (SectioningStatusCookie.getInstance().getSortBy(iOnline, 0) > 0);
            EnrollmentComparator.SortBy sort = EnrollmentComparator.SortBy
                    .values()[Math.abs(SectioningStatusCookie.getInstance().getSortBy(iOnline, 0)) - 1];
            switch (sort) {
            case COURSE:
                iCourseTable.sort(hCourse, new EnrollmentComparator(sort), asc);
                break;
            case SUBJECT:
                iCourseTable.sort(hSubject, new EnrollmentComparator(sort), asc);
                break;
            case TITLE:
                iCourseTable.sort(hTitleSubpart, new EnrollmentComparator(sort), asc);
                break;
            case CONSENT:
                iCourseTable.sort(hRoom, new EnrollmentComparator(sort), asc);
                break;
            case LIMIT:
                iCourseTable.sort(hLimit, new EnrollmentComparator(sort), asc);
                break;
            case PROJECTION:
                iCourseTable.sort(hProjection, new EnrollmentComparator(sort), asc);
                break;
            case ENROLLMENT:
                iCourseTable.sort(hEnrollment, new EnrollmentComparator(sort), asc);
                break;
            case NEED_CONSENT:
                iCourseTable.sort(hConsent, new EnrollmentComparator(sort), asc);
                break;
            case RESERVATION:
                iCourseTable.sort(hReserved, new EnrollmentComparator(sort), asc);
                break;
            case WAITLIST:
                iCourseTable.sort(hWaitListed, new EnrollmentComparator(sort), asc);
                break;
            }
        }
    }

    public void populateStudentTable(List<StudentInfo> result) {
        List<Widget> header = new ArrayList<Widget>();

        if (iOnline && iProperties != null && iProperties.isCanSelectStudent()) {
            UniTimeTableHeader hSelect = new UniTimeTableHeader("&otimes;", HasHorizontalAlignment.ALIGN_CENTER);
            header.add(hSelect);
            hSelect.setWidth("10px");
            hSelect.addAdditionalStyleName("unitime-NoPrint");
            hSelect.addOperation(new Operation() {
                @Override
                public String getName() {
                    return MESSAGES.selectAll();
                }

                @Override
                public boolean hasSeparator() {
                    return false;
                }

                @Override
                public boolean isApplicable() {
                    return iSelectedStudentIds.size() != iStudentTable.getRowCount() + 2;
                }

                @Override
                public void execute() {
                    iSelectedStudentIds.clear();
                    for (int row = 0; row < iStudentTable.getRowCount(); row++) {
                        StudentInfo i = iStudentTable.getData(row);
                        if (i != null && i.getStudent() != null) {
                            ((CheckBox) iStudentTable.getWidget(row, 0)).setValue(true);
                            iSelectedStudentIds.add(i.getStudent().getId());
                        }
                    }
                }
            });
            hSelect.addOperation(new Operation() {
                @Override
                public String getName() {
                    return MESSAGES.clearAll();
                }

                @Override
                public boolean hasSeparator() {
                    return false;
                }

                @Override
                public boolean isApplicable() {
                    return iSelectedStudentIds.size() > 0;
                }

                @Override
                public void execute() {
                    iSelectedStudentIds.clear();
                    for (int row = 0; row < iStudentTable.getRowCount(); row++) {
                        StudentInfo i = iStudentTable.getData(row);
                        if (i != null && i.getStudent() != null)
                            ((CheckBox) iStudentTable.getWidget(row, 0)).setValue(false);
                    }
                }
            });
            hSelect.addOperation(new Operation() {
                @Override
                public String getName() {
                    return MESSAGES.sendStudentEmail();
                }

                @Override
                public boolean hasSeparator() {
                    return true;
                }

                @Override
                public boolean isApplicable() {
                    return iSelectedStudentIds.size() > 0 && iProperties != null && iProperties.isEmail();
                }

                @Override
                public void execute() {
                    SimpleForm sf = new SimpleForm();
                    final UniTimeHeaderPanel buttons = new UniTimeHeaderPanel();
                    sf.removeStyleName("unitime-NotPrintableBottomLine");
                    sf.addRow(MESSAGES.emailSubject(), iSubject);
                    if (iSubject.getText().isEmpty()
                            || iSubject.getText().equals(MESSAGES.defaulSubjectMassCancel()))
                        iSubject.setText(MESSAGES.defaulSubject());
                    sf.addRow(MESSAGES.emailCC(), iCC);
                    sf.addRow(MESSAGES.emailBody(), iMessage);
                    sf.addBottomRow(buttons);
                    final UniTimeDialogBox dialog = new UniTimeDialogBox(true, false);
                    dialog.setWidget(sf);
                    dialog.setText(MESSAGES.sendStudentEmail());
                    dialog.setEscapeToHide(true);
                    buttons.addButton("send", MESSAGES.emailSend(), new ClickHandler() {
                        @Override
                        public void onClick(ClickEvent event) {
                            List<Long> studentIds = new ArrayList<Long>();
                            for (int row = 0; row < iStudentTable.getRowCount(); row++) {
                                StudentInfo i = iStudentTable.getData(row);
                                if (i != null && i.getStudent() != null
                                        && iSelectedStudentIds.contains(i.getStudent().getId())) {
                                    studentIds.add(i.getStudent().getId());
                                    iStudentTable.setWidget(row, iStudentTable.getCellCount(row) - 1,
                                            new Image(RESOURCES.loading_small()));
                                }
                            }
                            dialog.hide();
                            sendEmail(studentIds.iterator(), iSubject.getText(), iMessage.getText(), iCC.getText(),
                                    0);
                        }
                    });
                    buttons.addButton("close", MESSAGES.buttonClose(), new ClickHandler() {
                        @Override
                        public void onClick(ClickEvent event) {
                            dialog.hide();
                        }
                    });
                    dialog.center();
                }
            });
            hSelect.addOperation(new Operation() {
                @Override
                public String getName() {
                    return MESSAGES.massCancel();
                }

                @Override
                public boolean hasSeparator() {
                    return false;
                }

                @Override
                public boolean isApplicable() {
                    return iSelectedStudentIds.size() > 0 && iProperties != null && iProperties.isMassCancel();
                }

                @Override
                public void execute() {
                    SimpleForm sf = new SimpleForm();
                    final UniTimeHeaderPanel buttons = new UniTimeHeaderPanel();
                    sf.removeStyleName("unitime-NotPrintableBottomLine");
                    if (iSubject.getText().isEmpty() || iSubject.getText().equals(MESSAGES.defaulSubject()))
                        iSubject.setText(MESSAGES.defaulSubjectMassCancel());
                    sf.addRow(MESSAGES.emailSubject(), iSubject);
                    sf.addRow(MESSAGES.emailCC(), iCC);
                    sf.addRow(MESSAGES.emailBody(), iMessage);
                    sf.addRow(MESSAGES.newStatus(), iStatus);
                    sf.addBottomRow(buttons);
                    final UniTimeDialogBox dialog = new UniTimeDialogBox(true, false);
                    dialog.setWidget(sf);
                    dialog.setText(MESSAGES.massCancel());
                    dialog.setEscapeToHide(true);
                    buttons.addButton("cancel", MESSAGES.buttonMassCancel(), new ClickHandler() {
                        @Override
                        public void onClick(ClickEvent event) {
                            if (!Window.confirm(MESSAGES.massCancelConfirmation())) {
                                dialog.hide();
                                return;
                            }

                            final List<Long> studentIds = new ArrayList<Long>();
                            for (int row = 0; row < iStudentTable.getRowCount(); row++) {
                                StudentInfo i = iStudentTable.getData(row);
                                if (i != null && i.getStudent() != null
                                        && iSelectedStudentIds.contains(i.getStudent().getId())) {
                                    studentIds.add(i.getStudent().getId());
                                    iStudentTable.setWidget(row, iStudentTable.getCellCount(row) - 1,
                                            new Image(RESOURCES.loading_small()));
                                }
                            }
                            dialog.hide();

                            LoadingWidget.getInstance().show(MESSAGES.massCanceling());
                            iSectioningService.massCancel(studentIds,
                                    iStatus.getWidget().getValue(iStatus.getWidget().getSelectedIndex()),
                                    iSubject.getText(), iMessage.getText(), iCC.getText(),
                                    new AsyncCallback<Boolean>() {

                                        @Override
                                        public void onFailure(Throwable caught) {
                                            LoadingWidget.getInstance().hide();
                                            UniTimeNotifications.error(caught);
                                            for (int row = 0; row < iStudentTable.getRowCount(); row++) {
                                                StudentInfo i = iStudentTable.getData(row);
                                                if (i != null && i.getStudent() != null
                                                        && studentIds.contains(i.getStudent().getId())) {
                                                    HTML error = new HTML(caught.getMessage());
                                                    error.setStyleName("unitime-ErrorMessage");
                                                    iStudentTable.setWidget(row,
                                                            iStudentTable.getCellCount(row) - 1, error);
                                                    i.setEmailDate(null);
                                                }
                                            }
                                        }

                                        @Override
                                        public void onSuccess(Boolean result) {
                                            LoadingWidget.getInstance().hide();
                                            loadData();
                                        }
                                    });
                        }
                    });
                    buttons.addButton("close", MESSAGES.buttonClose(), new ClickHandler() {
                        @Override
                        public void onClick(ClickEvent event) {
                            dialog.hide();
                        }
                    });
                    dialog.center();
                }
            });
            hSelect.addOperation(new Operation() {
                @Override
                public String getName() {
                    return MESSAGES.requestStudentUpdate();
                }

                @Override
                public boolean hasSeparator() {
                    return true;
                }

                @Override
                public boolean isApplicable() {
                    return iSelectedStudentIds.size() > 0 && iProperties != null && iProperties.isRequestUpdate();
                }

                @Override
                public void execute() {
                    List<Long> studentIds = new ArrayList<Long>(iSelectedStudentIds);
                    LoadingWidget.getInstance().show(MESSAGES.requestingStudentUpdate());
                    iSectioningService.requestStudentUpdate(studentIds, new AsyncCallback<Boolean>() {
                        @Override
                        public void onFailure(Throwable caught) {
                            LoadingWidget.getInstance().hide();
                            UniTimeNotifications.error(caught);
                        }

                        @Override
                        public void onSuccess(Boolean result) {
                            LoadingWidget.getInstance().hide();
                            UniTimeNotifications.info(MESSAGES.requestStudentUpdateSuccess());
                        }
                    });
                }
            });
            if (iStates != null) {
                for (final String ref : new TreeSet<String>(iStates.keySet())) {
                    hSelect.addOperation(new Operation() {
                        @Override
                        public String getName() {
                            return MESSAGES.changeStatusTo(iStates.get(ref));
                        }

                        @Override
                        public boolean hasSeparator() {
                            return "".equals(ref);
                        }

                        @Override
                        public boolean isApplicable() {
                            return iSelectedStudentIds.size() > 0 && iProperties != null
                                    && iProperties.isChangeStatus();
                        }

                        @Override
                        public void execute() {
                            List<Long> studentIds = new ArrayList<Long>(iSelectedStudentIds);
                            LoadingWidget.getInstance().show(MESSAGES.changingStatusTo(iStates.get(ref)));
                            iSectioningService.changeStatus(studentIds, ref, new AsyncCallback<Boolean>() {

                                @Override
                                public void onFailure(Throwable caught) {
                                    LoadingWidget.getInstance().hide();
                                    UniTimeNotifications.error(caught);
                                }

                                @Override
                                public void onSuccess(Boolean result) {
                                    for (int row = 0; row < iStudentTable.getRowCount(); row++) {
                                        StudentInfo i = iStudentTable.getData(row);
                                        if (i != null && i.getStudent() != null
                                                && ((CheckBox) iStudentTable.getWidget(row, 0)).getValue()) {
                                            i.setStatus(ref);
                                            ((HTML) iStudentTable.getWidget(row, iStatusColumn)).setHTML(ref);
                                        }
                                    }
                                    LoadingWidget.getInstance().hide();
                                }
                            });
                        }
                    });
                }
            }
        }

        boolean hasExtId = false;
        for (ClassAssignmentInterface.StudentInfo e : result) {
            if (e.getStudent() != null && e.getStudent().isCanShowExternalId()) {
                hasExtId = true;
                break;
            }
        }

        UniTimeTableHeader hExtId = null;
        if (hasExtId) {
            hExtId = new UniTimeTableHeader(MESSAGES.colStudentExternalId());
            header.add(hExtId);
            addSortOperation(hExtId, StudentComparator.SortBy.EXTERNAL_ID, MESSAGES.colStudentExternalId());
        }

        UniTimeTableHeader hStudent = new UniTimeTableHeader(MESSAGES.colStudent());
        header.add(hStudent);
        addSortOperation(hStudent, StudentComparator.SortBy.STUDENT, MESSAGES.colStudent());

        UniTimeTableHeader hTotal = new UniTimeTableHeader("&nbsp;");
        header.add(hTotal);

        boolean hasEnrollment = false, hasWaitList = false, hasArea = false, hasMajor = false, hasGroup = false,
                hasAcmd = false, hasReservation = false, hasRequestedDate = false, hasEnrolledDate = false,
                hasConsent = false, hasCredit = false;
        for (ClassAssignmentInterface.StudentInfo e : result) {
            if (e.getStudent() == null)
                continue;
            // if (e.getStatus() != null) hasStatus = true;
            if (e.getTotalEnrollment() != null && e.getTotalEnrollment() > 0)
                hasEnrollment = true;
            if (e.getTotalUnassigned() != null && e.getTotalUnassigned() > 0)
                hasWaitList = true;
            if (e.getStudent().hasArea())
                hasArea = true;
            if (e.getStudent().hasMajor())
                hasMajor = true;
            if (e.getStudent().hasGroup())
                hasGroup = true;
            if (e.getStudent().hasAccommodation())
                hasAcmd = true;
            if (e.getTotalReservation() != null && e.getTotalReservation() > 0)
                hasReservation = true;
            if (e.getRequestedDate() != null)
                hasRequestedDate = true;
            if (e.getEnrolledDate() != null)
                hasEnrolledDate = true;
            // if (e.getEmailDate() != null) hasEmailDate = true;
            if (e.getTotalConsentNeeded() != null && e.getTotalConsentNeeded() > 0)
                hasConsent = true;
            if (e.hasTotalCredit())
                hasCredit = true;
        }

        UniTimeTableHeader hArea = null, hClasf = null;
        if (hasArea) {
            hArea = new UniTimeTableHeader(MESSAGES.colArea());
            //hArea.setWidth("100px");
            header.add(hArea);
            addSortOperation(hArea, StudentComparator.SortBy.AREA, MESSAGES.colArea());

            hClasf = new UniTimeTableHeader(MESSAGES.colClassification());
            //hClasf.setWidth("100px");
            header.add(hClasf);
            addSortOperation(hClasf, StudentComparator.SortBy.CLASSIFICATION, MESSAGES.colClassification());
        }

        UniTimeTableHeader hMajor = null;
        if (hasMajor) {
            hMajor = new UniTimeTableHeader(MESSAGES.colMajor());
            //hMajor.setWidth("100px");
            header.add(hMajor);
            addSortOperation(hMajor, StudentComparator.SortBy.MAJOR, MESSAGES.colMajor());
        }

        UniTimeTableHeader hGroup = null;
        if (hasGroup) {
            hGroup = new UniTimeTableHeader(MESSAGES.colGroup());
            //hGroup.setWidth("100px");
            header.add(hGroup);
            addSortOperation(hGroup, StudentComparator.SortBy.GROUP, MESSAGES.colGroup());
        }

        UniTimeTableHeader hAcmd = null;
        if (hasAcmd) {
            hAcmd = new UniTimeTableHeader(MESSAGES.colAccommodation());
            //hGroup.setWidth("100px");
            header.add(hAcmd);
            addSortOperation(hAcmd, StudentComparator.SortBy.ACCOMODATION, MESSAGES.colAccommodation());
        }

        iStatusColumn = header.size() - 1;
        UniTimeTableHeader hStatus = new UniTimeTableHeader(MESSAGES.colStatus());
        //hMajor.setWidth("100px");
        header.add(hStatus);
        addSortOperation(hStatus, StudentComparator.SortBy.STATUS, MESSAGES.colStatus());

        UniTimeTableHeader hEnrollment = null;
        if (hasEnrollment) {
            hEnrollment = new UniTimeTableHeader(MESSAGES.colEnrollment());
            header.add(hEnrollment);
            addSortOperation(hEnrollment, StudentComparator.SortBy.ENROLLMENT, MESSAGES.colEnrollment());
        }

        UniTimeTableHeader hWaitlist = null;
        if (hasWaitList) {
            hWaitlist = new UniTimeTableHeader(MESSAGES.colWaitListed());
            header.add(hWaitlist);
            addSortOperation(hWaitlist, StudentComparator.SortBy.WAITLIST, MESSAGES.colWaitListed());
        }

        UniTimeTableHeader hReservation = null;
        if (hasReservation) {
            hReservation = new UniTimeTableHeader(MESSAGES.colReservation());
            header.add(hReservation);
            addSortOperation(hReservation, StudentComparator.SortBy.RESERVATION, MESSAGES.colReservation());
        }

        UniTimeTableHeader hConsent = null;
        if (hasConsent) {
            hConsent = new UniTimeTableHeader(MESSAGES.colConsent());
            header.add(hConsent);
            addSortOperation(hConsent, StudentComparator.SortBy.CONSENT, MESSAGES.colConsent());
        }

        UniTimeTableHeader hCredit = null;
        if (hasCredit) {
            hCredit = new UniTimeTableHeader(MESSAGES.colCredit());
            header.add(hCredit);
            addSortOperation(hCredit, StudentComparator.SortBy.CREDIT, MESSAGES.colCredit());
        }

        UniTimeTableHeader hRequestTS = null;
        if (hasRequestedDate) {
            hRequestTS = new UniTimeTableHeader(MESSAGES.colRequestTimeStamp());
            header.add(hRequestTS);
            addSortOperation(hRequestTS, StudentComparator.SortBy.REQUEST_TS, MESSAGES.colRequestTimeStamp());
        }

        UniTimeTableHeader hEnrolledTS = null;
        if (hasEnrolledDate) {
            hEnrolledTS = new UniTimeTableHeader(MESSAGES.colEnrollmentTimeStamp());
            header.add(hEnrolledTS);
            addSortOperation(hEnrolledTS, StudentComparator.SortBy.ENROLLMENT_TS,
                    MESSAGES.colEnrollmentTimeStamp());
        }

        UniTimeTableHeader hEmailTS = null;
        if (iOnline) {
            hEmailTS = new UniTimeTableHeader(MESSAGES.colEmailTimeStamp());
            header.add(hEmailTS);
            addSortOperation(hEmailTS, StudentComparator.SortBy.EMAIL_TS, MESSAGES.colEmailTimeStamp());
        }

        iStudentTable.addRow(null, header);

        Set<Long> newlySelected = new HashSet<Long>();
        for (StudentInfo info : result) {
            List<Widget> line = new ArrayList<Widget>();
            if (info.getStudent() != null) {
                if (iOnline && iProperties != null && iProperties.isCanSelectStudent()) {
                    CheckBox ch = new CheckBox();
                    ch.addClickHandler(new ClickHandler() {
                        @Override
                        public void onClick(ClickEvent event) {
                            event.stopPropagation();
                        }
                    });
                    final Long sid = info.getStudent().getId();
                    if (iSelectedStudentIds.contains(sid)) {
                        ch.setValue(true);
                        newlySelected.add(sid);
                    }
                    ch.addClickHandler(new ClickHandler() {
                        @Override
                        public void onClick(ClickEvent event) {
                            event.stopPropagation();
                        }
                    });
                    ch.addValueChangeHandler(new ValueChangeHandler<Boolean>() {
                        @Override
                        public void onValueChange(ValueChangeEvent<Boolean> event) {
                            if (event.getValue())
                                iSelectedStudentIds.add(sid);
                            else
                                iSelectedStudentIds.remove(sid);
                        }
                    });
                    line.add(ch);
                }
                if (hasExtId) {
                    line.add(new Label(
                            info.getStudent().isCanShowExternalId() ? info.getStudent().getExternalId() : "",
                            false));
                }
                line.add(new TitleCell(info.getStudent().getName()));
                if (hasArea) {
                    line.add(new HTML(info.getStudent().getArea("<br>"), false));
                    line.add(new HTML(info.getStudent().getClassification("<br>"), false));
                }
                if (hasMajor)
                    line.add(new HTML(info.getStudent().getMajor("<br>"), false));
                if (hasGroup)
                    line.add(new HTML(info.getStudent().getGroup("<br>"), false));
                if (hasAcmd)
                    line.add(new HTML(info.getStudent().getAccommodation("<br>"), false));
                line.add(new HTML(info.getStatus(), false));
            } else {
                if (iOnline && iProperties != null && iProperties.isCanSelectStudent())
                    line.add(new HTML("&nbsp;", false));
                if (hasExtId)
                    line.add(new TitleCell(MESSAGES.total()));
                else
                    line.add(new Label(MESSAGES.total()));
                line.add(new NumberCell(null, result.size() - 1));
                if (hasArea) {
                    line.add(new HTML("&nbsp;", false));
                    line.add(new HTML("&nbsp;", false));
                }
                if (hasMajor)
                    line.add(new HTML("&nbsp;", false));
                if (hasGroup)
                    line.add(new HTML("&nbsp;", false));
                if (hasAcmd)
                    line.add(new HTML("&nbsp;", false));
                line.add(new HTML("&nbsp;", false));
            }
            if (hasEnrollment)
                line.add(new NumberCell(info.getEnrollment(), info.getTotalEnrollment()));
            if (hasWaitList)
                line.add(new WaitListCell(info));
            if (hasReservation)
                line.add(new NumberCell(info.getReservation(), info.getTotalReservation()));
            if (hasConsent)
                line.add(new NumberCell(info.getConsentNeeded(), info.getTotalConsentNeeded()));
            if (hasCredit)
                line.add(new CreditCell(info.getCredit(), info.getTotalCredit()));
            if (info.getStudent() != null) {
                if (hasRequestedDate)
                    line.add(new HTML(
                            info.getRequestedDate() == null ? "&nbsp;" : sDF.format(info.getRequestedDate()),
                            false));
                if (hasEnrolledDate)
                    line.add(new HTML(
                            info.getEnrolledDate() == null ? "&nbsp;" : sDF.format(info.getEnrolledDate()), false));
                if (iOnline)
                    line.add(new HTML(info.getEmailDate() == null ? "&nbsp;" : sDF.format(info.getEmailDate()),
                            false));
            } else {
                if (hasRequestedDate)
                    line.add(new HTML("&nbsp;", false));
                if (hasEnrolledDate)
                    line.add(new HTML("&nbsp;", false));
                if (iOnline)
                    line.add(new HTML("&nbsp;", false));
            }
            iStudentTable.addRow(info, line);
        }
        iSelectedStudentIds.clear();
        iSelectedStudentIds.addAll(newlySelected);

        if (iStudentTable.getRowCount() >= 2) {
            for (int c = 0; c < iStudentTable.getCellCount(iStudentTable.getRowCount() - 1); c++)
                iStudentTable.getCellFormatter().setStyleName(iStudentTable.getRowCount() - 1, c,
                        "unitime-TotalRow");
        }

        iStudentTableHint.setVisible(hasWaitList);

        if (SectioningStatusCookie.getInstance().getSortBy(iOnline, 1) != 0) {
            boolean asc = (SectioningStatusCookie.getInstance().getSortBy(iOnline, 1) > 0);
            StudentComparator.SortBy sort = StudentComparator.SortBy
                    .values()[Math.abs(SectioningStatusCookie.getInstance().getSortBy(iOnline, 1)) - 1];
            UniTimeTableHeader h = null;
            switch (sort) {
            case ACCOMODATION:
                h = hAcmd;
                break;
            case AREA:
                h = hArea;
                break;
            case CLASSIFICATION:
                h = hClasf;
                break;
            case CONSENT:
                h = hConsent;
                break;
            case CREDIT:
                h = hCredit;
                break;
            case EMAIL_TS:
                h = hEmailTS;
                break;
            case ENROLLMENT:
                h = hEnrollment;
                break;
            case ENROLLMENT_TS:
                h = hEnrolledTS;
                break;
            case EXTERNAL_ID:
                h = hExtId;
                break;
            case GROUP:
                h = hGroup;
                break;
            case MAJOR:
                h = hMajor;
                break;
            case REQUEST_TS:
                h = hRequestTS;
                break;
            case RESERVATION:
                h = hReservation;
                break;
            case STATUS:
                h = hStatus;
                break;
            case STUDENT:
                h = hStudent;
                break;
            case WAITLIST:
                h = hWaitlist;
                break;
            }
            if (h != null)
                iStudentTable.sort(h, new StudentComparator(sort), asc);
        }
    }

    public void populateChangeLog(List<SectioningAction> result) {
        List<UniTimeTableHeader> header = new ArrayList<UniTimeTableHeader>();

        UniTimeTableHeader hStudent = new UniTimeTableHeader(MESSAGES.colStudent());
        header.add(hStudent);
        addSortOperation(hStudent, ChangeLogComparator.SortBy.STUDENT, MESSAGES.colStudent());

        UniTimeTableHeader hOp = new UniTimeTableHeader(MESSAGES.colOperation());
        header.add(hOp);
        addSortOperation(hOp, ChangeLogComparator.SortBy.OPERATION, MESSAGES.colOperation());

        UniTimeTableHeader hTimeStamp = new UniTimeTableHeader(MESSAGES.colTimeStamp());
        header.add(hTimeStamp);
        addSortOperation(hTimeStamp, ChangeLogComparator.SortBy.TIME_STAMP, MESSAGES.colTimeStamp());

        UniTimeTableHeader hResult = new UniTimeTableHeader(MESSAGES.colResult());
        header.add(hResult);
        addSortOperation(hResult, ChangeLogComparator.SortBy.RESULT, MESSAGES.colResult());

        UniTimeTableHeader hUser = new UniTimeTableHeader(MESSAGES.colUser());
        header.add(hUser);
        addSortOperation(hUser, ChangeLogComparator.SortBy.USER, MESSAGES.colUser());

        final UniTimeTableHeader hMessage = new UniTimeTableHeader(MESSAGES.colMessage());
        header.add(hMessage);
        addSortOperation(hMessage, ChangeLogComparator.SortBy.MESSAGE, MESSAGES.colMessage());

        iLogTable.addRow(null, header);

        for (ClassAssignmentInterface.SectioningAction log : result) {
            iLogTable.addRow(log, new TopCell(log.getStudent().getName()), new TopCell(log.getOperation()),
                    new TopCell(sTSF.format(log.getTimeStamp())), new TopCell(log.getResult()),
                    new TopCell(log.getUser() == null ? "" : log.getUser()),
                    new HTML(log.getMessage() == null ? "" : log.getMessage()));
        }

        if (SectioningStatusCookie.getInstance().getSortBy(iOnline, 2) != 0) {
            boolean asc = (SectioningStatusCookie.getInstance().getSortBy(iOnline, 2) > 0);
            ChangeLogComparator.SortBy sort = ChangeLogComparator.SortBy
                    .values()[Math.abs(SectioningStatusCookie.getInstance().getSortBy(iOnline, 2)) - 1];
            UniTimeTableHeader h = null;
            switch (sort) {
            case MESSAGE:
                h = hMessage;
                break;
            case OPERATION:
                h = hOp;
                break;
            case RESULT:
                h = hResult;
                break;
            case STUDENT:
                h = hStudent;
                break;
            case TIME_STAMP:
                h = hTimeStamp;
                break;
            case USER:
                h = hUser;
                break;
            }
            if (h != null)
                iLogTable.sort(h, new ChangeLogComparator(sort), asc);
        }
    }

    protected void addSortOperation(final UniTimeTableHeader header, final EnrollmentComparator.SortBy sort,
            final String column) {
        header.addOperation(new Operation() {
            @Override
            public void execute() {
                iCourseTable.sort(header, new EnrollmentComparator(sort));
                SectioningStatusCookie.getInstance().setSortBy(iOnline, 0,
                        header.getOrder() ? 1 + sort.ordinal() : -1 - sort.ordinal());
            }

            @Override
            public boolean isApplicable() {
                return true;
            }

            @Override
            public boolean hasSeparator() {
                return false;
            }

            @Override
            public String getName() {
                return MESSAGES.sortBy(column);
            }
        });
    }

    protected void addSortOperation(final UniTimeTableHeader header, final StudentComparator.SortBy sort,
            final String column) {
        header.addOperation(new Operation() {
            @Override
            public void execute() {
                iStudentTable.sort(header, new StudentComparator(sort));
                SectioningStatusCookie.getInstance().setSortBy(iOnline, 1,
                        header.getOrder() ? 1 + sort.ordinal() : -1 - sort.ordinal());
            }

            @Override
            public boolean isApplicable() {
                return true;
            }

            @Override
            public boolean hasSeparator() {
                return false;
            }

            @Override
            public String getName() {
                return MESSAGES.sortBy(column);
            }
        });
    }

    public static class SimpleSuggestion implements Suggestion {
        private String iDisplay, iReplace;

        public SimpleSuggestion(String display, String replace) {
            iDisplay = display;
            iReplace = replace;
        }

        public SimpleSuggestion(String replace) {
            this(replace, replace);
        }

        public String getDisplayString() {
            return iDisplay;
        }

        public String getReplacementString() {
            return iReplace;
        }
    }

    protected void addSortOperation(final UniTimeTableHeader header, final ChangeLogComparator.SortBy sort,
            final String column) {
        header.addOperation(new Operation() {
            @Override
            public void execute() {
                iLogTable.sort(header, new ChangeLogComparator(sort));
                SectioningStatusCookie.getInstance().setSortBy(iOnline, 2,
                        header.getOrder() ? 1 + sort.ordinal() : -1 - sort.ordinal());
            }

            @Override
            public boolean isApplicable() {
                return true;
            }

            @Override
            public boolean hasSeparator() {
                return false;
            }

            @Override
            public String getName() {
                return MESSAGES.sortBy(column);
            }
        });
    }

    public class SuggestCallback implements AsyncCallback<List<String[]>> {
        private Request iRequest;
        private Callback iCallback;

        public SuggestCallback(Request request, Callback callback) {
            iRequest = request;
            iCallback = callback;
        }

        public void onFailure(Throwable caught) {
            ArrayList<Suggestion> suggestions = new ArrayList<Suggestion>();
            // suggestions.add(new SimpleSuggestion("<font color='red'>"+caught.getMessage()+"</font>", ""));
            iCallback.onSuggestionsReady(iRequest, new Response(suggestions));
            ToolBox.checkAccess(caught);
        }

        public void onSuccess(List<String[]> result) {
            ArrayList<Suggestion> suggestions = new ArrayList<Suggestion>();
            for (String[] suggestion : result) {
                suggestions.add(new SimpleSuggestion(suggestion[1], suggestion[0]));
            }
            iCallback.onSuggestionsReady(iRequest, new Response(suggestions));
        }

    }

    public static class NumberCell extends HTML implements HasCellAlignment {

        public NumberCell(Integer value, Integer total) {
            super();
            if (value == null) {
                if (total != null)
                    setHTML(total == 0 ? "-" : total < 0 ? "&infin;" : total.toString());
            } else {
                if (value.equals(total))
                    setHTML(total == 0 ? "-" : total < 0 ? "&infin;" : total.toString());
                else
                    setHTML((value < 0 ? "&infin;" : value.toString()) + " / "
                            + (total < 0 ? "&infin;" : total.toString()));
            }
        }

        @Override
        public HorizontalAlignmentConstant getCellAlignment() {
            return HasHorizontalAlignment.ALIGN_RIGHT;
        }
    }

    public static class TitleCell extends HTML implements HasColSpan {

        public TitleCell(String title) {
            super(title);
        }

        @Override
        public int getColSpan() {
            return 2;
        }
    }

    public static class AvailableCell extends HTML implements HasCellAlignment {
        public AvailableCell(EnrollmentInfo e) {
            super();
            int other = (e.getOther() == null ? 0 : e.getOther());
            if (e.getLimit() == null) {
                setHTML("-");
                setTitle(MESSAGES.availableNoLimit());
            } else if (e.getLimit() < 0) {
                if (e.getAvailable() != null && e.getAvailable() == 0) {
                    setHTML("&infin;" + MESSAGES.htmlReservationSign());
                    setTitle(MESSAGES.availableUnlimitedWithReservation());
                } else {
                    setHTML("&infin;");
                    setTitle(MESSAGES.availableUnlimited());
                }
            } else {
                if (e.getAvailable() == e.getLimit() - e.getTotalEnrollment() - other) {
                    setHTML(e.getAvailable() + " / " + e.getLimit());
                    if (e.getAvailable() == 0)
                        setTitle(MESSAGES.availableNot(e.getLimit()));
                    else
                        setTitle(MESSAGES.available(e.getAvailable(), e.getLimit()));
                } else if (e.getAvailable() == 0 && e.getLimit() > e.getTotalEnrollment() + other) {
                    setHTML((e.getLimit() - e.getTotalEnrollment() - other) + MESSAGES.htmlReservationSign() + " / "
                            + e.getLimit());
                    setTitle(MESSAGES.availableWithReservation(e.getLimit() - e.getTotalEnrollment() - other,
                            e.getLimit()));
                } else {
                    setHTML(e.getAvailable() + " + "
                            + (e.getLimit() - e.getTotalEnrollment() - e.getAvailable() - other)
                            + MESSAGES.htmlReservationSign() + " / " + e.getLimit());
                    setTitle(MESSAGES.availableSomeReservation(e.getAvailable(), e.getLimit(),
                            e.getLimit() - e.getTotalEnrollment() - e.getAvailable() - other));
                }
            }
        }

        public static boolean hasReservedSpace(EnrollmentInfo e) {
            if (e.getLimit() < 0)
                return false;
            if (e.getLimit() < 0) {
                return e.getAvailable() == 0;
            } else {
                return e.getAvailable() != e.getLimit() - e.getTotalEnrollment()
                        - (e.getOther() == null ? 0 : e.getOther());
            }

        }

        @Override
        public HorizontalAlignmentConstant getCellAlignment() {
            return HasHorizontalAlignment.ALIGN_RIGHT;
        }
    }

    public static class WaitListCell extends HTML implements HasCellAlignment {
        public WaitListCell(int wait, int tWait, int unasg, int tUnasg, Integer topWaitingPriority) {
            super();
            if (tWait == 0 || tWait == tUnasg) {
                // no wait-list or all wait-listed
                if (unasg == tUnasg) {
                    setHTML(unasg == 0 ? "-" : String.valueOf(unasg));
                } else {
                    setHTML(unasg + " / " + tUnasg);
                }
                if (tWait > 0)
                    setHTML(getHTML() + MESSAGES.htmlWaitListSign());
            } else {
                if (wait == tWait && unasg == tUnasg) {
                    setHTML(wait == 0 ? String.valueOf(unasg)
                            : wait == unasg ? wait + MESSAGES.htmlWaitListSign()
                                    : (unasg - wait) + " + " + wait + MESSAGES.htmlWaitListSign());
                } else {
                    setHTML((wait == 0 ? String.valueOf(unasg)
                            : wait == unasg ? wait + MESSAGES.htmlWaitListSign()
                                    : (unasg - wait) + " + " + wait + MESSAGES.htmlWaitListSign())
                            + " / " + tUnasg);
                }
            }
            if (topWaitingPriority != null)
                setHTML(getHTML() + " " + MESSAGES.firstWaitListedPrioritySign(topWaitingPriority));
        }

        public WaitListCell(StudentInfo e) {
            this(e.hasWaitlist() ? e.getWaitlist() : 0, e.hasTotalWaitlist() ? e.getTotalWaitlist() : 0,
                    e.hasUnassigned() ? e.getUnassigned() : 0, e.hasTotalUnassigned() ? e.getTotalUnassigned() : 0,
                    e.getTopWaitingPriority());
        }

        public WaitListCell(EnrollmentInfo e) {
            this(e.hasWaitlist() ? e.getWaitlist() : 0, e.hasTotalWaitlist() ? e.getTotalWaitlist() : 0,
                    e.hasUnassigned() ? e.getUnassigned() : 0, e.hasTotalUnassigned() ? e.getTotalUnassigned() : 0,
                    null);
        }

        @Override
        public HorizontalAlignmentConstant getCellAlignment() {
            return HasHorizontalAlignment.ALIGN_RIGHT;
        }
    }

    public static class EnrollmentComparator implements Comparator<EnrollmentInfo> {
        public enum SortBy {
            SUBJECT, COURSE, TITLE, CONSENT, LIMIT, PROJECTION, ENROLLMENT, WAITLIST, RESERVATION, NEED_CONSENT
        }

        private SortBy iSortBy;

        public EnrollmentComparator(SortBy sortBy) {
            iSortBy = sortBy;
        }

        @Override
        public int compare(EnrollmentInfo e1, EnrollmentInfo e2) {
            // Totals line is always last
            if (e1.getCourseId() == null)
                return 1;
            if (e2.getCourseId() == null)
                return -1;

            if (e1.getCourseId().equals(e2.getCourseId())) { // Same course
                // Course line first
                if (e1.getConfigId() == null)
                    return -1;
                if (e2.getConfigId() == null)
                    return -1;
                return compareClasses(e1, e2);
            } else { // Different course
                return compareCourses(e1, e2);
            }
        }

        private int compareClasses(EnrollmentInfo e1, EnrollmentInfo e2) {
            return 0;
        }

        private int compareCourses(EnrollmentInfo e1, EnrollmentInfo e2) {
            int cmp;
            switch (iSortBy) {
            case SUBJECT:
                break;
            case COURSE:
                cmp = e1.getCourseNbr().compareTo(e2.getCourseNbr());
                if (cmp != 0)
                    return cmp;
                break;
            case TITLE:
                cmp = (e1.getTitle() == null ? "" : e1.getTitle())
                        .compareTo(e2.getTitle() == null ? "" : e2.getTitle());
                if (cmp != 0)
                    return cmp;
                break;
            case CONSENT:
                cmp = (e1.getConsent() == null ? "" : e1.getConsent())
                        .compareTo(e2.getConsent() == null ? "" : e2.getConsent());
                if (cmp != 0)
                    return cmp;
                break;
            case LIMIT:
                cmp = (e1.getAvailable() == null ? new Integer(0)
                        : e1.getAvailable() < 0 ? new Integer(Integer.MAX_VALUE) : e1.getAvailable())
                                .compareTo(e2.getAvailable() == null ? 0
                                        : e2.getAvailable() < 0 ? Integer.MAX_VALUE : e2.getAvailable());
                if (cmp != 0)
                    return cmp;
                cmp = (e1.getLimit() == null ? new Integer(0) : e1.getLimit())
                        .compareTo(e2.getLimit() == null ? 0 : e2.getLimit());
                if (cmp != 0)
                    return cmp;
                break;
            case PROJECTION:
                cmp = (e1.getProjection() == null ? new Integer(0) : e1.getProjection())
                        .compareTo(e2.getProjection() == null ? 0 : e2.getProjection());
                if (cmp != 0)
                    return -cmp;
                break;
            case ENROLLMENT:
                cmp = (e1.getEnrollment() == null ? new Integer(0) : e1.getEnrollment())
                        .compareTo(e2.getEnrollment() == null ? 0 : e2.getEnrollment());
                if (cmp != 0)
                    return -cmp;
                cmp = (e1.getTotalEnrollment() == null ? new Integer(0) : e1.getTotalEnrollment())
                        .compareTo(e2.getTotalEnrollment() == null ? 0 : e2.getTotalEnrollment());
                if (cmp != 0)
                    return -cmp;
                break;
            case WAITLIST:
                cmp = (e1.getWaitlist() == null ? new Integer(0) : e1.getWaitlist())
                        .compareTo(e2.getWaitlist() == null ? 0 : e2.getWaitlist());
                if (cmp != 0)
                    return -cmp;
                cmp = (e1.getUnassigned() == null ? new Integer(0) : e1.getUnassigned())
                        .compareTo(e2.getUnassigned() == null ? 0 : e2.getUnassigned());
                if (cmp != 0)
                    return -cmp;
                cmp = (e1.getTotalWaitlist() == null ? new Integer(0) : e1.getTotalWaitlist())
                        .compareTo(e2.getTotalWaitlist() == null ? 0 : e2.getTotalWaitlist());
                if (cmp != 0)
                    return -cmp;
                cmp = (e1.getTotalUnassigned() == null ? new Integer(0) : e1.getTotalUnassigned())
                        .compareTo(e2.getTotalUnassigned() == null ? 0 : e2.getTotalUnassigned());
                if (cmp != 0)
                    return -cmp;
                break;
            case RESERVATION:
                cmp = (e1.getReservation() == null ? new Integer(0) : e1.getReservation())
                        .compareTo(e2.getReservation() == null ? 0 : e2.getReservation());
                if (cmp != 0)
                    return -cmp;
                cmp = (e1.getTotalReservation() == null ? new Integer(0) : e1.getTotalReservation())
                        .compareTo(e2.getTotalReservation() == null ? 0 : e2.getTotalReservation());
                if (cmp != 0)
                    return -cmp;
                break;
            case NEED_CONSENT:
                cmp = (e1.getConsentNeeded() == null ? new Integer(0) : new Integer(e1.getConsentNeeded()))
                        .compareTo(e2.getConsentNeeded() == null ? 0 : e2.getConsentNeeded());
                if (cmp != 0)
                    return -cmp;
                cmp = (e1.getTotalConsentNeeded() == null ? new Integer(0)
                        : new Integer(e1.getTotalConsentNeeded()))
                                .compareTo(e2.getTotalConsentNeeded() == null ? 0 : e2.getTotalConsentNeeded());
                if (cmp != 0)
                    return -cmp;
                break;
            }

            // Default sort
            cmp = e1.getSubject().compareTo(e2.getSubject());
            if (cmp != 0)
                return cmp;

            cmp = e1.getCourseNbr().compareTo(e2.getCourseNbr());
            if (cmp != 0)
                return cmp;

            return 0;
        }

    }

    public static class StudentComparator implements Comparator<StudentInfo> {
        public enum SortBy {
            EXTERNAL_ID, STUDENT, AREA, CLASSIFICATION, MAJOR, GROUP, ACCOMODATION, STATUS, ENROLLMENT, WAITLIST, RESERVATION, CONSENT, CREDIT, REQUEST_TS, ENROLLMENT_TS, EMAIL_TS,;
        }

        private SortBy iSortBy;

        public StudentComparator(SortBy sortBy) {
            iSortBy = sortBy;
        }

        protected int doCompare(StudentInfo e1, StudentInfo e2) {
            int cmp;
            switch (iSortBy) {
            case EXTERNAL_ID:
                return (e1.getStudent().isCanShowExternalId() ? e1.getStudent().getExternalId() : "")
                        .compareTo(e2.getStudent().isCanShowExternalId() ? e2.getStudent().getExternalId() : "");
            case STUDENT:
                return e1.getStudent().getName().compareTo(e2.getStudent().getName());
            case AREA:
                cmp = e1.getStudent().getAreaClasf("|").compareTo(e2.getStudent().getAreaClasf("|"));
                if (cmp != 0)
                    return cmp;
                return e1.getStudent().getMajor("|").compareTo(e2.getStudent().getMajor("|"));
            case CLASSIFICATION:
                cmp = e1.getStudent().getClassification("|").compareTo(e2.getStudent().getClassification("|"));
                if (cmp != 0)
                    return cmp;
                cmp = e1.getStudent().getArea("|").compareTo(e2.getStudent().getArea("|"));
                if (cmp != 0)
                    return cmp;
                return e1.getStudent().getMajor("|").compareTo(e2.getStudent().getMajor("|"));
            case MAJOR:
                cmp = e1.getStudent().getMajor("|").compareTo(e2.getStudent().getMajor("|"));
                if (cmp != 0)
                    return cmp;
                return e1.getStudent().getAreaClasf("|").compareTo(e2.getStudent().getAreaClasf("|"));
            case GROUP:
                cmp = e1.getStudent().getGroup("|").compareTo(e2.getStudent().getGroup("|"));
                if (cmp != 0)
                    return cmp;
                return e1.getStudent().getAreaClasf("|").compareTo(e2.getStudent().getAreaClasf("|"));
            case ACCOMODATION:
                cmp = e1.getStudent().getAccommodation("|").compareTo(e2.getStudent().getAccommodation("|"));
                if (cmp != 0)
                    return cmp;
                return e1.getStudent().getAreaClasf("|").compareTo(e2.getStudent().getAreaClasf("|"));
            case STATUS:
                return (e1.getStatus() == null ? "" : e1.getStatus())
                        .compareToIgnoreCase(e2.getStatus() == null ? "" : e2.getStatus());
            case ENROLLMENT:
                cmp = (e1.getEnrollment() == null ? new Integer(0) : e1.getEnrollment())
                        .compareTo(e2.getEnrollment() == null ? 0 : e2.getEnrollment());
                if (cmp != 0)
                    return -cmp;
                return (e1.getTotalEnrollment() == null ? new Integer(0) : e1.getTotalEnrollment())
                        .compareTo(e2.getTotalEnrollment() == null ? 0 : e2.getTotalEnrollment());
            case WAITLIST:
                cmp = (e1.getUnassigned() == null ? new Integer(0) : e1.getUnassigned())
                        .compareTo(e2.getUnassigned() == null ? 0 : e2.getUnassigned());
                if (cmp != 0)
                    return -cmp;
                cmp = (e1.getWaitlist() == null ? new Integer(0) : e1.getWaitlist())
                        .compareTo(e2.getWaitlist() == null ? 0 : e2.getWaitlist());
                if (cmp != 0)
                    return -cmp;
                cmp = (e1.getTotalUnassigned() == null ? new Integer(0) : e1.getTotalUnassigned())
                        .compareTo(e2.getTotalUnassigned() == null ? 0 : e2.getTotalUnassigned());
                if (cmp != 0)
                    return -cmp;
                cmp = (e1.getTotalWaitlist() == null ? new Integer(0) : e1.getTotalWaitlist())
                        .compareTo(e2.getTotalWaitlist() == null ? 0 : e2.getTotalWaitlist());
                if (cmp != 0)
                    return -cmp;
                return (e1.getTopWaitingPriority() == null ? new Integer(Integer.MAX_VALUE)
                        : e1.getTopWaitingPriority())
                                .compareTo(e2.getTopWaitingPriority() == null ? Integer.MAX_VALUE
                                        : e2.getTopWaitingPriority());
            case RESERVATION:
                cmp = (e1.getReservation() == null ? new Integer(0) : e1.getReservation())
                        .compareTo(e2.getReservation() == null ? 0 : e2.getReservation());
                if (cmp != 0)
                    return -cmp;
                return (e1.getTotalReservation() == null ? new Integer(0) : e1.getTotalReservation())
                        .compareTo(e2.getTotalReservation() == null ? 0 : e2.getTotalReservation());
            case CONSENT:
                cmp = (e1.getConsentNeeded() == null ? new Integer(0) : e1.getConsentNeeded())
                        .compareTo(e2.getConsentNeeded() == null ? 0 : e2.getConsentNeeded());
                if (cmp != 0)
                    return -cmp;
                return (e1.getTotalConsentNeeded() == null ? new Integer(0) : e1.getTotalConsentNeeded())
                        .compareTo(e2.getTotalConsentNeeded() == null ? 0 : e2.getTotalConsentNeeded());
            case CREDIT:
                cmp = (e1.hasCredit() ? e1.getCredit() : new Float(0f))
                        .compareTo(e2.hasCredit() ? e2.getCredit() : new Float(0f));
                if (cmp != 0)
                    return -cmp;
                return (e1.hasTotalCredit() ? e1.getTotalCredit() : new Float(0f))
                        .compareTo(e2.hasTotalCredit() ? e2.getTotalCredit() : new Float(0f));
            case REQUEST_TS:
                return (e1.getRequestedDate() == null ? new Date(0) : e1.getRequestedDate())
                        .compareTo(e2.getRequestedDate() == null ? new Date(0) : e2.getRequestedDate());
            case ENROLLMENT_TS:
                return (e1.getEnrolledDate() == null ? new Date(0) : e1.getEnrolledDate())
                        .compareTo(e2.getEnrolledDate() == null ? new Date(0) : e2.getEnrolledDate());
            case EMAIL_TS:
                return (e1.getEmailDate() == null ? new Date(0) : e1.getEmailDate())
                        .compareTo(e2.getEmailDate() == null ? new Date(0) : e2.getEmailDate());
            default:
                return 0;
            }
        }

        @Override
        public int compare(StudentInfo e1, StudentInfo e2) {
            if (e1.getStudent() == null)
                return 1;
            if (e2.getStudent() == null)
                return -1;
            int cmp = doCompare(e1, e2);
            if (cmp != 0)
                return cmp;
            cmp = e1.getStudent().getName().compareTo(e2.getStudent().getName());
            if (cmp != 0)
                return cmp;
            return (e1.getStudent().getId() < e2.getStudent().getId() ? -1 : 1);
        }
    }

    public static class ChangeLogComparator implements Comparator<SectioningAction> {
        public enum SortBy {
            STUDENT, OPERATION, TIME_STAMP, RESULT, USER, MESSAGE,;
        }

        private SortBy iSortBy;

        public ChangeLogComparator(SortBy sortBy) {
            iSortBy = sortBy;
        }

        public int doCompare(SectioningAction e1, SectioningAction e2) {
            switch (iSortBy) {
            case STUDENT:
                return e1.getStudent().getName().compareTo(e2.getStudent().getName());
            case OPERATION:
                return e1.getOperation().compareTo(e2.getOperation());
            case TIME_STAMP:
                return -e1.getTimeStamp().compareTo(e2.getTimeStamp());
            case RESULT:
                return (e1.getResult() == null ? "" : e1.getResult())
                        .compareTo(e2.getResult() == null ? "" : e2.getResult());
            case MESSAGE:
                return (e1.getMessage() == null ? "" : e1.getMessage())
                        .compareTo(e2.getMessage() == null ? "" : e2.getMessage());
            default:
                return 0;
            }
        }

        @Override
        public int compare(SectioningAction e1, SectioningAction e2) {
            int cmp = doCompare(e1, e2);
            if (cmp != 0)
                return cmp;
            return -e1.getTimeStamp().compareTo(e2.getTimeStamp());
        }
    }

    private void sendEmail(final Iterator<Long> studentIds, final String subject, final String message,
            final String cc, final int fails) {
        if (!studentIds.hasNext())
            return;
        final Long studentId = studentIds.next();
        iSectioningService.sendEmail(studentId, subject, message, cc, new AsyncCallback<Boolean>() {

            @Override
            public void onFailure(Throwable caught) {
                for (int row = 0; row < iStudentTable.getRowCount(); row++) {
                    StudentInfo i = iStudentTable.getData(row);
                    if (i != null && i.getStudent() != null && studentId.equals(i.getStudent().getId())) {
                        HTML error = new HTML(caught.getMessage());
                        error.setStyleName("unitime-ErrorMessage");
                        iStudentTable.setWidget(row, iStudentTable.getCellCount(row) - 1, error);
                        i.setEmailDate(null);
                    }
                }
                if (fails >= 4) {
                    while (studentIds.hasNext()) {
                        Long sid = studentIds.next();
                        for (int row = 0; row < iStudentTable.getRowCount(); row++) {
                            StudentInfo i = iStudentTable.getData(row);
                            if (i != null && i.getStudent() != null && sid.equals(i.getStudent().getId())) {
                                HTML error = new HTML(MESSAGES.exceptionCancelled(caught.getMessage()));
                                error.setStyleName("unitime-ErrorMessage");
                                iStudentTable.setWidget(row, iStudentTable.getCellCount(row) - 1, error);
                                i.setEmailDate(null);
                            }
                        }
                    }
                }
                sendEmail(studentIds, subject, message, cc, fails + 1);
            }

            @Override
            public void onSuccess(Boolean result) {
                for (int row = 0; row < iStudentTable.getRowCount(); row++) {
                    StudentInfo i = iStudentTable.getData(row);
                    if (i != null && i.getStudent() != null && studentId.equals(i.getStudent().getId())) {
                        if (result) {
                            i.setEmailDate(new Date());
                            iStudentTable.setWidget(row, iStudentTable.getCellCount(row) - 1,
                                    new HTML(sDF.format(i.getEmailDate()), false));
                        } else {
                            HTML error = new HTML(MESSAGES.exceptionNoEmail());
                            error.setStyleName("unitime-ErrorMessage");
                            iStudentTable.setWidget(row, iStudentTable.getCellCount(row) - 1, error);
                            i.setEmailDate(null);
                        }
                    }
                    sendEmail(studentIds, subject, message, cc, fails);
                }
            }
        });
    }

    public static class CreditCell extends HTML implements HasCellAlignment {
        private static NumberFormat df = NumberFormat.getFormat("0.#");

        public CreditCell(Float value, Float total) {
            super();
            if (total != null && total > 0f) {
                if (total.equals(value))
                    setHTML(df.format(total));
                else
                    setHTML(df.format(value) + " / " + df.format(total));
            } else {
                setHTML("&nbsp;");
            }
        }

        @Override
        public HorizontalAlignmentConstant getCellAlignment() {
            return HasHorizontalAlignment.ALIGN_RIGHT;
        }
    }
}