org.unitime.timetable.gwt.client.events.EventResourceTimetable.java Source code

Java tutorial

Introduction

Here is the source code for org.unitime.timetable.gwt.client.events.EventResourceTimetable.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.events;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
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.Client;
import org.unitime.timetable.gwt.client.Lookup;
import org.unitime.timetable.gwt.client.ToolBox;
import org.unitime.timetable.gwt.client.aria.AriaTabBar;
import org.unitime.timetable.gwt.client.events.AcademicSessionSelectionBox.AcademicSession;
import org.unitime.timetable.gwt.client.events.AcademicSessionSelectionBox.AcademicSessionFilter;
import org.unitime.timetable.gwt.client.events.EventMeetingTable.EventMeetingRow;
import org.unitime.timetable.gwt.client.events.StartEndTimeSelector.StartEndTime;
import org.unitime.timetable.gwt.client.events.TimeGrid.MeetingClickEvent;
import org.unitime.timetable.gwt.client.events.TimeGrid.MeetingClickHandler;
import org.unitime.timetable.gwt.client.page.UniTimeNotifications;
import org.unitime.timetable.gwt.client.page.UniTimePageHeader;
import org.unitime.timetable.gwt.client.page.UniTimePageLabel;
import org.unitime.timetable.gwt.client.widgets.FilterBox;
import org.unitime.timetable.gwt.client.widgets.IntervalSelector;
import org.unitime.timetable.gwt.client.widgets.LoadingWidget;
import org.unitime.timetable.gwt.client.widgets.SimpleForm;
import org.unitime.timetable.gwt.client.widgets.ServerDateTimeFormat;
import org.unitime.timetable.gwt.client.widgets.TimeSelector.TimeUtils;
import org.unitime.timetable.gwt.client.widgets.UniTimeDialogBox;
import org.unitime.timetable.gwt.client.widgets.UniTimeHeaderPanel;
import org.unitime.timetable.gwt.client.widgets.UniTimeWidget;
import org.unitime.timetable.gwt.client.widgets.FilterBox.Chip;
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.WeekSelector;
import org.unitime.timetable.gwt.command.client.GwtRpcResponse;
import org.unitime.timetable.gwt.command.client.GwtRpcResponseList;
import org.unitime.timetable.gwt.command.client.GwtRpcService;
import org.unitime.timetable.gwt.command.client.GwtRpcServiceAsync;
import org.unitime.timetable.gwt.resources.GwtAriaMessages;
import org.unitime.timetable.gwt.resources.GwtConstants;
import org.unitime.timetable.gwt.resources.GwtMessages;
import org.unitime.timetable.gwt.shared.EventInterface;
import org.unitime.timetable.gwt.shared.EventInterface.ContactInterface;
import org.unitime.timetable.gwt.shared.EventInterface.FilterRpcResponse;
import org.unitime.timetable.gwt.shared.EventInterface.RequestSessionDetails;
import org.unitime.timetable.gwt.shared.EventInterface.SaveFilterDefaultRpcRequest;
import org.unitime.timetable.gwt.shared.EventInterface.SaveOrApproveEventRpcResponse;
import org.unitime.timetable.gwt.shared.EventInterface.SelectionInterface;
import org.unitime.timetable.gwt.shared.PersonInterface;
import org.unitime.timetable.gwt.shared.AcademicSessionProvider.AcademicSessionChangeEvent;
import org.unitime.timetable.gwt.shared.AcademicSessionProvider.AcademicSessionChangeHandler;
import org.unitime.timetable.gwt.shared.EventInterface.ApprovalStatus;
import org.unitime.timetable.gwt.shared.EventInterface.ApproveEventRpcRequest;
import org.unitime.timetable.gwt.shared.EventInterface.EncodeQueryRpcResponse;
import org.unitime.timetable.gwt.shared.EventInterface.EventDetailRpcRequest;
import org.unitime.timetable.gwt.shared.EventInterface.EventType;
import org.unitime.timetable.gwt.shared.EventInterface.FilterRpcRequest;
import org.unitime.timetable.gwt.shared.EventInterface.MeetingInterface;
import org.unitime.timetable.gwt.shared.EventInterface.EncodeQueryRpcRequest;
import org.unitime.timetable.gwt.shared.EventInterface.EventPropertiesRpcRequest;
import org.unitime.timetable.gwt.shared.EventInterface.EventLookupRpcRequest;
import org.unitime.timetable.gwt.shared.EventInterface.MessageInterface;
import org.unitime.timetable.gwt.shared.EventInterface.ResourceLookupRpcRequest;
import org.unitime.timetable.gwt.shared.EventInterface.ResourceInterface;
import org.unitime.timetable.gwt.shared.EventInterface.ResourceType;
import org.unitime.timetable.gwt.shared.EventInterface.EventPropertiesRpcResponse;
import org.unitime.timetable.gwt.shared.EventInterface.SessionMonth;
import org.unitime.timetable.gwt.shared.EventInterface.WeekInterface;

import com.google.gwt.aria.client.Id;
import com.google.gwt.aria.client.Roles;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.Style.Cursor;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.dom.client.ChangeEvent;
import com.google.gwt.event.dom.client.ChangeHandler;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.FocusEvent;
import com.google.gwt.event.dom.client.FocusHandler;
import com.google.gwt.event.dom.client.KeyUpEvent;
import com.google.gwt.event.dom.client.KeyUpHandler;
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.user.client.Command;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.History;
import com.google.gwt.user.client.TakesValue;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.Window.Location;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.DockPanel;
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.Label;
import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.MenuBar;
import com.google.gwt.user.client.ui.MenuItem;
import com.google.gwt.user.client.ui.PopupPanel;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.SuggestBox;
import com.google.gwt.user.client.ui.SuggestOracle;
import com.google.gwt.user.client.ui.TextArea;
import com.google.gwt.user.client.ui.UIObject;
import com.google.gwt.user.client.ui.SuggestOracle.Suggestion;

/**
 * @author Tomas Muller
 */
public class EventResourceTimetable extends Composite
        implements EventMeetingTable.MeetingFilter, EventAdd.EventPropertiesProvider, AcademicSessionFilter {
    private static GwtAriaMessages ARIA = GWT.create(GwtAriaMessages.class);
    private static final GwtConstants CONSTANTS = GWT.create(GwtConstants.class);
    private static final GwtMessages MESSAGES = GWT.create(GwtMessages.class);
    private static final GwtRpcServiceAsync RPC = GWT.create(GwtRpcService.class);

    private SimplePanel iRootPanel;

    private EventDetail iEventDetail;
    private EventAdd iEventAdd;

    private SimpleForm iPanel, iFilter;
    private DockPanel iDockPanel;
    private SimplePanel iGridOrTablePanel;
    private UniTimeHeaderPanel iHeader, iFooter, iFilterHeader;
    private TimeGrid iTimeGrid;
    private EventMeetingTable iTable;
    private ResourceInterface iResource;
    private List<EventInterface> iData;
    private WeekSelector iWeekPanel;
    private RoomSelector iRoomPanel;
    private AcademicSessionSelectionBox iSession;
    private ListBox iResourceTypes;
    private SuggestBox iResources;
    private int iResourcesRow = -1;
    private EventFilterBox iEvents = null;
    private RoomFilterBox iRooms = null;
    private String iLocDate = null, iLocRoom = null;
    private MeetingClickHandler iMeetingClickHandler;
    private Lookup iLookup;
    private AriaTabBar iTabBar;
    private ApproveDialog iApproveDialog;
    private int iSessionRow = -1;
    private Set<Integer> iMatchingWeeks = new HashSet<Integer>();
    private Set<Long> iMatchingRooms = new HashSet<Long>();

    private EventPropertiesRpcResponse iProperties = null;
    private HistoryToken iHistoryToken = null;
    private PageType iType = null;
    private boolean iInitialized = false;
    private List<EventInterface> iBack = new ArrayList<EventInterface>();
    private List<SessionMonth> iSessionMonths = null;

    public static enum PageType {
        Timetable("tab", "0", "title", MESSAGES.pageEventTimetable(), "rooms", "", "showClear", "true"), Events(
                "filter", "events", "rooms", "flag:Event", "events", "mode:\"My Events\"", "type", "room", "title",
                MESSAGES.pageEvents(), "fixedTitle", "true", "fixedType", "true", "tab", "1", "showClear", "true",
                "saveAsDefault", "events,rooms"), RoomTimetable("type", "room", "fixedType", "true", "title",
                        MESSAGES.pageRoomTimetable(), "showClear", "true"), Classes("type", "subject", "fixedType",
                                "true", "events", "type:Class", "tab", "1", "filter", "classes", "rooms", "",
                                "title", MESSAGES.pageClasses(), "fixedTitle", "true", "addEvent", "false",
                                "showFilter", "false", "showClear", "false"), Exams("type", "subject", "fixedType",
                                        "true", "events", "type:\"Final Exam\" type:\"Midterm Exam\"", "tab", "1",
                                        "filter", "exams", "rooms", "", "title", MESSAGES.pageExaminations(),
                                        "fixedTitle", "true", "addEvent", "false", "showFilter", "false",
                                        "showClear", "false"), Personal("type", "person", "fixedType", "true",
                                                "events", "", "filter", "person", "rooms", "", "title",
                                                MESSAGES.pagePersonalTimetable(), "addEvent", "false", "fixedTitle",
                                                "true", "showFilter", "false", "showClear", "false"), Availability(
                                                        "title", MESSAGES.pageEventRoomAvailability(), "rooms",
                                                        "flag:Event");

        String[] iParams = null;

        PageType(String... params) {
            iParams = params;
        }

        public String[] getParams() {
            return iParams;
        }
    }

    public EventResourceTimetable(PageType type) {
        iHistoryToken = new HistoryToken(type);
        iType = type;

        iFilter = new SimpleForm(2);
        iFilter.removeStyleName("unitime-NotPrintableBottomLine");
        iFilter.getColumnFormatter().setWidth(0, "120px");

        iFilterHeader = new UniTimeHeaderPanel(MESSAGES.sectFilter());
        iLookup = new Lookup();
        iLookup.setOptions("mustHaveExternalId");
        iLookup.addValueChangeHandler(new ValueChangeHandler<PersonInterface>() {
            @Override
            public void onValueChange(ValueChangeEvent<PersonInterface> event) {
                if (event.getValue() != null) {
                    iResources.setText(event.getValue().getId());
                    resourceTypeChanged(true);
                }
            }
        });
        iFilterHeader.addButton("lookup", MESSAGES.buttonLookup(), 75, new ClickHandler() {
            @Override
            public void onClick(ClickEvent event) {
                iLookup.center();
            }
        });
        iFilterHeader.setEnabled("lookup", false);
        iFilterHeader.addButton("add", MESSAGES.buttonAddEvent(), 75, new ClickHandler() {
            @Override
            public void onClick(ClickEvent event) {
                iEventAdd.setEvent(null);
                iEventAdd.show();
            }
        });
        iFilterHeader.setEnabled("add", false);
        iFilter.addHeaderRow(iFilterHeader);
        iFilterHeader.addButton("clear", MESSAGES.buttonClear(), 75, new ClickHandler() {
            @Override
            public void onClick(ClickEvent event) {
                // Event filter
                iEvents.setValue(iHistoryToken.getDefaultParameter("events", ""), true);

                // Room filter
                iRooms.setValue(iHistoryToken.getDefaultParameter("rooms", ""), true);

                // Resource type
                if (!"true".equals(iHistoryToken.getParameter("fixedType", "false"))) {
                    String typeString = iHistoryToken.getDefaultParameter("type", "room");
                    if (typeString != null)
                        for (int idx = 0; idx < iResourceTypes.getItemCount(); idx++) {
                            if (iResourceTypes.getValue(idx).equalsIgnoreCase(typeString)) {
                                iResourceTypes.setSelectedIndex(idx);
                            }
                        }
                }

                // Subject, curriculum, department, etc.
                iResources.setValue(iHistoryToken.getDefaultParameter("name", ""));

                // Room selection
                iLocRoom = iHistoryToken.getDefaultParameter("room", "");

                // Week selection
                iLocDate = iHistoryToken.getDefaultParameter("date", "");

                hideResults();
                changeUrl();
                UniTimePageLabel.getInstance().setPageName(getPageName());
                resourceTypeChanged(false);
            }
        });
        iFilterHeader.setEnabled("clear", "true".equals(iHistoryToken.getParameter("showClear", "true")));
        iFilterHeader.addButton("search", MESSAGES.buttonSearch(), 75, new ClickHandler() {
            @Override
            public void onClick(ClickEvent event) {
                resourceTypeChanged(true);
            }
        });

        iSession = new AcademicSessionSelectionBox(iHistoryToken.getParameter("term"), "Events") {
            @Override
            protected void onInitializationSuccess(List<AcademicSession> sessions) {
                iFilter.setVisible(sessions != null && !sessions.isEmpty());
                UniTimePageHeader.getInstance().getRight().setVisible(false);
                UniTimePageHeader.getInstance().getRight().setPreventDefault(true);
            }

            @Override
            protected void onInitializationFailure(Throwable caught) {
                UniTimeNotifications.error(MESSAGES.failedLoadSessions(caught.getMessage()), caught);
            }
        };
        iSession.setFilter(this);
        iSessionRow = iFilter.addRow(MESSAGES.propAcademicSession(), iSession);

        iEvents = new EventFilterBox(iSession);
        if ("true".equals(iHistoryToken.getParameter("showFilter", "true")))
            iFilter.addRow(MESSAGES.propEventFilter(), iEvents);

        iRooms = new RoomFilterBox(iSession);
        if ("true".equals(iHistoryToken.getParameter("showFilter", "true")))
            iFilter.addRow(MESSAGES.propRoomFilter(), iRooms);

        iResourceTypes = new ListBox();
        for (ResourceType resource : ResourceType.values()) {
            if (resource.isVisible())
                iResourceTypes.addItem(resource.getResourceType(CONSTANTS), resource.name());
        }

        iResourceTypes.addChangeHandler(new ChangeHandler() {
            @Override
            public void onChange(ChangeEvent event) {
                iResources.setText("");
                UniTimePageLabel.getInstance().setPageName(getPageName());
                hideResults();
                if (iProperties != null)
                    resourceTypeChanged(isShowingResults());
            }
        });
        int row = iFilter.addRow(MESSAGES.propResourceType(), iResourceTypes);
        if ("true".equals(iHistoryToken.getParameter("fixedType", "false")))
            iFilter.getRowFormatter().setVisible(row, false);

        iResources = new SuggestBox(new SuggestOracle() {
            @Override
            public void requestDefaultSuggestions(Request request, Callback callback) {
                requestSuggestions(request, callback);
            }

            @Override
            public void requestSuggestions(final Request request, final Callback callback) {
                if (iSession.getAcademicSessionId() != null) {
                    RPC.execute(
                            ResourceLookupRpcRequest.findResources(iSession.getAcademicSessionId(),
                                    ResourceType
                                            .valueOf(iResourceTypes.getValue(iResourceTypes.getSelectedIndex())),
                                    request.getQuery(), request.getLimit()),
                            new AsyncCallback<GwtRpcResponseList<ResourceInterface>>() {
                                @Override
                                public void onFailure(final Throwable caught) {
                                    ArrayList<Suggestion> suggestions = new ArrayList<Suggestion>();
                                    suggestions.add(new Suggestion() {
                                        @Override
                                        public String getDisplayString() {
                                            return "<font color='red'>" + caught.getMessage() + "</font>";
                                        }

                                        @Override
                                        public String getReplacementString() {
                                            return "";
                                        }
                                    });
                                    callback.onSuggestionsReady(request, new Response(suggestions));
                                    ToolBox.checkAccess(caught);
                                }

                                @Override
                                public void onSuccess(GwtRpcResponseList<ResourceInterface> result) {
                                    ArrayList<Suggestion> suggestions = new ArrayList<Suggestion>();
                                    for (ResourceInterface resource : result) {
                                        suggestions.add(new ResourceSuggestion(resource));
                                    }
                                    callback.onSuggestionsReady(request, new Response(suggestions));
                                }
                            });
                }
            }

            @Override
            public boolean isDisplayStringHTML() {
                return true;
            }
        });
        iResources.getValueBox().addFocusHandler(new FocusHandler() {
            @Override
            public void onFocus(FocusEvent event) {
                iResources.showSuggestionList();
            }
        });
        iResourcesRow = iFilter.addRow(MESSAGES.propResource(), iResources);
        iResources.addSelectionHandler(new SelectionHandler<Suggestion>() {
            public void onSelection(SelectionEvent<Suggestion> event) {
                if (event.getSelectedItem() instanceof ResourceSuggestion) {
                    resourceChanged(((ResourceSuggestion) event.getSelectedItem()).getResource());
                }
            }
        });
        iFilter.getRowFormatter().setVisible(iResourcesRow, false);

        iResourceTypes.setSelectedIndex(ResourceType.ROOM.ordinal());

        iPanel = new SimpleForm(2);
        iPanel.removeStyleName("unitime-NotPrintableBottomLine");
        iPanel.addRow(iFilter);
        iHeader = new UniTimeHeaderPanel();
        iPanel.addHeaderRow(iHeader);
        iWeekPanel = new WeekSelector(iSession);
        iWeekPanel.addValueChangeHandler(new ValueChangeHandler<WeekSelector.Interval>() {
            @Override
            public void onValueChange(ValueChangeEvent<WeekSelector.Interval> e) {
                iLocDate = iWeekPanel.getSelection();
                tabOrDataChanged(true);
            }
        });
        iWeekPanel.setFilter(new WeekSelector.Filter<WeekInterface>() {
            @Override
            public boolean filter(WeekInterface week) {
                return !iMatchingWeeks.contains(week.getDayOfYear());
            }

            @Override
            public boolean isEmpty() {
                return iMatchingRooms.isEmpty();
            }
        });
        iRoomPanel = new RoomSelector();
        iRoomPanel.setFilter(new WeekSelector.Filter<ResourceInterface>() {
            @Override
            public boolean filter(ResourceInterface room) {
                return !iMatchingRooms.contains(room.getId());
            }

            @Override
            public boolean isEmpty() {
                return iMatchingRooms.isEmpty();
            }
        });

        iTabBar = new AriaTabBar();
        iTabBar.addTab(MESSAGES.tabGrid(), true);
        iTabBar.addTab(MESSAGES.tabEventTable(), true);
        iTabBar.addTab(MESSAGES.tabMeetingTable(), true);
        iTabBar.addSelectionHandler(new SelectionHandler<Integer>() {
            @Override
            public void onSelection(SelectionEvent<Integer> event) {
                tabOrDataChanged(true);
            }
        });

        iGridOrTablePanel = new SimplePanel();
        iGridOrTablePanel.setStyleName("unitime-TabPanel");

        final Character gridAccessKey = UniTimeHeaderPanel.guessAccessKey(MESSAGES.tabGrid());
        final Character eventsAccessKey = UniTimeHeaderPanel.guessAccessKey(MESSAGES.tabEventTable());
        final Character meetingsAccessKey = UniTimeHeaderPanel.guessAccessKey(MESSAGES.tabMeetingTable());
        if (gridAccessKey != null || eventsAccessKey != null || meetingsAccessKey != null) {
            RootPanel.get().addDomHandler(new KeyUpHandler() {
                @Override
                public void onKeyUp(KeyUpEvent event) {
                    if (!iRootPanel.getWidget().equals(iPanel))
                        return;
                    int tab = -1;
                    if (gridAccessKey != null && event.getNativeEvent().getCtrlKey()
                            && (event.getNativeKeyCode() == gridAccessKey
                                    || event.getNativeKeyCode() == Character.toUpperCase(gridAccessKey))) {
                        tab = 0;
                    }
                    if (eventsAccessKey != null && event.getNativeEvent().getCtrlKey()
                            && (event.getNativeKeyCode() == eventsAccessKey
                                    || event.getNativeKeyCode() == Character.toUpperCase(eventsAccessKey))) {
                        tab = 1;
                    }
                    if (meetingsAccessKey != null && event.getNativeEvent().getCtrlKey()
                            && (event.getNativeKeyCode() == meetingsAccessKey
                                    || event.getNativeKeyCode() == Character.toUpperCase(meetingsAccessKey))) {
                        tab = 2;
                    }
                    if (tab >= 0) {
                        iTabBar.selectTab(tab);
                        event.preventDefault();
                    }
                }
            }, KeyUpEvent.getType());
        }

        iDockPanel = new DockPanel();
        iDockPanel.setStyleName("unitime-EventResults");
        iDockPanel.setSpacing(0);
        iDockPanel.add(iGridOrTablePanel, DockPanel.SOUTH);
        iDockPanel.add(iRoomPanel, DockPanel.WEST);
        iDockPanel.setCellHorizontalAlignment(iRoomPanel, HasHorizontalAlignment.ALIGN_LEFT);
        iDockPanel.add(iTabBar, DockPanel.CENTER);
        iDockPanel.setCellVerticalAlignment(iTabBar, HasVerticalAlignment.ALIGN_BOTTOM);
        iDockPanel.setCellHorizontalAlignment(iTabBar, HasHorizontalAlignment.ALIGN_CENTER);
        iDockPanel.add(iWeekPanel, DockPanel.EAST);
        iDockPanel.setCellHorizontalAlignment(iWeekPanel, HasHorizontalAlignment.ALIGN_RIGHT);
        Roles.getTabpanelRole().set(iDockPanel.getElement());
        Roles.getTabpanelRole().setAriaOwnsProperty(iDockPanel.getElement(), Id.of(iTabBar.getElement()));

        iPanel.addRow(iDockPanel);

        hideResults();

        iFooter = iHeader.clonePanel();
        iPanel.addRow(iFooter);
        iRootPanel = new SimplePanel(iPanel);
        initWidget(iRootPanel);

        iRoomPanel.addValueChangeHandler(new ValueChangeHandler<IntervalSelector<ResourceInterface>.Interval>() {
            @Override
            public void onValueChange(ValueChangeEvent<IntervalSelector<ResourceInterface>.Interval> e) {
                iLocRoom = iRoomPanel.getSelection();
                tabOrDataChanged(true);
            }
        });

        for (int i = 1; i < iPanel.getRowCount(); i++)
            iPanel.getRowFormatter().setVisible(i, false);

        iHeader.addButton("add", MESSAGES.buttonAddEvent(), 75, new ClickHandler() {
            @Override
            public void onClick(ClickEvent event) {
                iEventAdd.setEvent(null);
                iEventAdd.show();
            }
        });
        iHeader.setEnabled("add", false);
        iHeader.addButton("print", MESSAGES.buttonPrint(), 75, new ClickHandler() {
            @Override
            public void onClick(ClickEvent clickEvent) {
                final EventMeetingTable table = new EventMeetingTable(
                        getSelectedTab() <= 1 ? EventMeetingTable.Mode.ListOfEvents
                                : EventMeetingTable.Mode.ListOfMeetings,
                        false, EventResourceTimetable.this);
                table.setMeetingFilter(EventResourceTimetable.this);
                table.setShowMainContact(iProperties != null && iProperties.isCanLookupMainContact());
                table.setEvents(iData);
                table.setSortBy(iTable.getSortBy());
                table.getElement().getStyle().setWidth(1040, Unit.PX);

                int firstSlot = 84, lastSlot = 216;
                boolean skipDays = iEvents.hasChip(new FilterBox.Chip("day", null));
                boolean hasDay[] = new boolean[] {
                        !skipDays || iEvents.hasChip(new FilterBox.Chip("day", CONSTANTS.longDays()[0])),
                        !skipDays || iEvents.hasChip(new FilterBox.Chip("day", CONSTANTS.longDays()[1])),
                        !skipDays || iEvents.hasChip(new FilterBox.Chip("day", CONSTANTS.longDays()[2])),
                        !skipDays || iEvents.hasChip(new FilterBox.Chip("day", CONSTANTS.longDays()[3])),
                        !skipDays || iEvents.hasChip(new FilterBox.Chip("day", CONSTANTS.longDays()[4])),
                        iEvents.hasChip(new FilterBox.Chip("day", CONSTANTS.longDays()[5])),
                        iEvents.hasChip(new FilterBox.Chip("day", CONSTANTS.longDays()[6])) };
                for (EventInterface event : iData) {
                    if (event.getType() == EventType.Unavailabile
                            && !iEvents.hasChip(new Chip("type", "Not Available")))
                        continue;
                    for (MeetingInterface meeting : event.getMeetings()) {
                        if (filterEvent(event, meeting))
                            continue;
                        if (meeting.getApprovalStatus() != ApprovalStatus.Pending
                                && meeting.getApprovalStatus() != ApprovalStatus.Approved)
                            continue;
                        if (firstSlot > meeting.getStartSlot())
                            firstSlot = meeting.getStartSlot();
                        if (lastSlot < meeting.getEndSlot())
                            lastSlot = meeting.getEndSlot();
                        hasDay[meeting.getDayOfWeek()] = true;
                    }
                }
                Chip after = iEvents.getChip("after");
                if (after != null) {
                    Integer slot = TimeUtils.parseTime(CONSTANTS, after.getValue(), null);
                    if (slot != null && firstSlot > slot)
                        firstSlot = slot;
                }
                Chip before = iEvents.getChip("before");
                if (before != null) {
                    Integer slot = TimeUtils.parseTime(CONSTANTS, before.getValue(), firstSlot);
                    if (slot != null && lastSlot < slot)
                        lastSlot = slot;
                }
                int nrDays = 0;
                for (boolean d : hasDay)
                    if (d)
                        nrDays++;
                int days[] = new int[nrDays];
                int d = 0;
                for (int i = 0; i < 7; i++)
                    if (hasDay[i])
                        days[d++] = i;
                int firstHour = firstSlot / 12;
                int lastHour = 1 + (lastSlot - 1) / 12;
                if (firstHour <= 7 && firstHour > 0 && ((firstSlot % 12) <= 6))
                    firstHour--;
                HashMap<Long, String> colors = new HashMap<Long, String>();

                final TimeGrid tg = new TimeGrid(colors, days, (int) (1000 / nrDays), 55, true, false,
                        (firstHour < 7 ? firstHour : 7), (lastHour > 18 ? lastHour : 18),
                        EventResourceTimetable.this);
                tg.setResourceType(getResourceType());
                tg.setSelectedWeeks(iWeekPanel.getSelected());
                tg.setRoomResources(iRoomPanel.getSelected());
                tg.setMode(gridMode());
                tg.showVerticalSplit();

                for (EventInterface event : sortedEvents()) {
                    List<MeetingInterface> meetings = new ArrayList<MeetingInterface>();
                    for (MeetingInterface meeting : event.getMeetings()) {
                        if (meeting.getApprovalStatus() != ApprovalStatus.Pending
                                && meeting.getApprovalStatus() != ApprovalStatus.Approved)
                            continue;
                        if (meeting.getMeetingDate() != null && !filterEvent(event, meeting))
                            meetings.add(meeting);
                    }
                    if (!meetings.isEmpty())
                        tg.addEvent(event, meetings);
                }
                if (iWeekPanel.getValue() != null)
                    tg.labelDays(iWeekPanel.getValue().getFirst(), iWeekPanel.getValue().getLast());

                // Move header row to thead
                Element headerRow = table.getRowFormatter().getElement(0);
                Element tableElement = table.getElement();
                Element thead = DOM.createTHead();
                tableElement.insertFirst(thead);
                headerRow.getParentElement().removeChild(headerRow);
                thead.appendChild(headerRow);

                ToolBox.print(new ToolBox.Page() {
                    @Override
                    public String getName() {
                        return iHeader.getHeaderTitle();
                    }

                    @Override
                    public String getUser() {
                        return iRoomPanel.getValue().toString();
                    }

                    @Override
                    public String getSession() {
                        return iWeekPanel.getValue().toString();
                    }

                    @Override
                    public Element getBody() {
                        return tg.getElement();
                    }
                }, new ToolBox.Page() {
                    @Override
                    public String getName() {
                        return iHeader.getHeaderTitle();
                    }

                    @Override
                    public String getUser() {
                        return iRoomPanel.getValue().toString();
                    }

                    @Override
                    public String getSession() {
                        return iWeekPanel.getValue().toString();
                    }

                    @Override
                    public Element getBody() {
                        return table.getElement();
                    }
                });
            }
        });

        iHeader.addButton("export", MESSAGES.buttonExport(), 75, new ClickHandler() {
            @Override
            public void onClick(ClickEvent clickEvent) {
                if (iProperties != null && iProperties.isCanExportCSV()) {
                    final PopupPanel popup = new PopupPanel(true);
                    MenuBar menu = new MenuBar(true);
                    MenuItem exportPdf = new MenuItem(MESSAGES.opExportPDF(), true, new Command() {
                        @Override
                        public void execute() {
                            popup.hide();
                            export("output=" + (getSelectedTab() <= 1 ? "events" : "meetings") + ".pdf&flags="
                                    + EventCookie.getInstance().getFlags(), true);
                        }
                    });
                    exportPdf.getElement().getStyle().setCursor(Cursor.POINTER);
                    menu.addItem(exportPdf);
                    MenuItem exportCsv = new MenuItem(MESSAGES.opExportCSV(), true, new Command() {
                        @Override
                        public void execute() {
                            popup.hide();
                            export("output=" + (getSelectedTab() <= 1 ? "events" : "meetings") + ".csv&flags="
                                    + EventCookie.getInstance().getFlags(), true);
                        }
                    });
                    exportCsv.getElement().getStyle().setCursor(Cursor.POINTER);
                    menu.addItem(exportCsv);
                    MenuItem exportIcs = new MenuItem(MESSAGES.opExportICalendar(), true, new Command() {
                        @Override
                        public void execute() {
                            popup.hide();
                            copyToClipboard(
                                    "output=events.ics"
                                            + (iType == PageType.Personal ? "&e:flag=All+Sessions" : ""),
                                    iType == PageType.Personal);
                        }
                    });
                    exportIcs.getElement().getStyle().setCursor(Cursor.POINTER);
                    menu.addItem(exportIcs);
                    popup.add(menu);
                    popup.showRelativeTo((UIObject) clickEvent.getSource());
                    menu.focus();
                } else {
                    export("output=events.ics", false);
                }
            }
        });
        iHeader.addButton("operations", MESSAGES.buttonMoreOperations(), 75, new ClickHandler() {
            @Override
            public void onClick(ClickEvent event) {
                final PopupPanel popup = new PopupPanel(true);
                iTable.getHeader(0).setMenu(popup);
                popup.showRelativeTo((UIObject) event.getSource());
                ((MenuBar) popup.getWidget()).focus();
            }
        });
        iHeader.setEnabled("print", false);
        iHeader.setEnabled("export", false);
        iHeader.setEnabled("operations", false);

        iSession.addAcademicSessionChangeHandler(new AcademicSessionChangeHandler() {
            @Override
            public void onAcademicSessionChange(AcademicSessionChangeEvent event) {
                loadProperties(new AsyncCallback<EventPropertiesRpcResponse>() {
                    @Override
                    public void onFailure(Throwable caught) {
                    }

                    @Override
                    public void onSuccess(EventPropertiesRpcResponse result) {
                        if (!iInitialized)
                            setup(true);
                        else
                            resourceTypeChanged(isShowingResults());
                    }
                });
            }
        });

        iEventDetail = new EventDetail(this) {
            @Override
            protected void onHide() {
                iRootPanel.setWidget(iPanel);
                UniTimePageLabel.getInstance().setPageName(getPageName());
                if (!isShowingResults())
                    resourceTypeChanged(true);
                changeUrl();
            }

            @Override
            protected void onShow() {
                iBack.clear();
                if (iTable != null)
                    iTable.clearHover();
                iRootPanel.setWidget(iEventDetail);
                changeUrl();
            }

            @Override
            protected void edit() {
                super.hide();
                iEventAdd.setEvent(getEvent());
                iEventAdd.show();
            }

            @Override
            protected EventInterface getPrevious(Long eventId) {
                return iTable.previous(eventId);
            }

            @Override
            protected EventInterface getNext(Long eventId) {
                return iTable.next(eventId);
            }

            @Override
            protected void previous(final EventInterface event) {
                iBack.add(getEvent());
                LoadingWidget.execute(
                        EventDetailRpcRequest.requestEventDetails(iSession.getAcademicSessionId(), event.getId()),
                        new AsyncCallback<EventInterface>() {
                            @Override
                            public void onFailure(Throwable caught) {
                                UniTimeNotifications
                                        .error(MESSAGES.failedLoad(event.getName(), caught.getMessage()), caught);
                            }

                            @Override
                            public void onSuccess(EventInterface result) {
                                LoadingWidget.getInstance().hide();
                                setEvent(result);
                                changeUrl();
                            }
                        }, MESSAGES.waitLoading(event.getName()));
            }

            @Override
            protected void next(final EventInterface event) {
                previous(event);
            }

            @Override
            public void hide() {
                if (!iBack.isEmpty()) {
                    EventInterface last = iBack.remove(iBack.size() - 1);
                    setEvent(last);
                } else {
                    super.hide();
                }
            }

            @Override
            protected void onApprovalOrReject(Long eventId, EventInterface event) {
                if (iData != null)
                    populate(tinker(new GwtRpcResponseList<EventInterface>(iData), eventId, event));
            }
        };

        iEventAdd = new EventAdd(iSession, this) {
            @Override
            protected void onHide() {
                iSession.setFilter(EventResourceTimetable.this);
                iFilter.setWidget(iSessionRow, 1, iSession);
                final EventInterface modified = iEventAdd.getEvent(), detail = iEventDetail.getEvent(),
                        saved = iEventAdd.getSavedEvent();
                if (saved != null) {
                    if (iData != null)
                        populate(tinker(new GwtRpcResponseList<EventInterface>(iData),
                                (saved.getId() == null ? modified.getId() : saved.getId()), saved));
                    if (saved.getId() != null) {
                        iEventDetail.setEvent(saved);
                        iEventDetail.show();
                    } else {
                        iRootPanel.setWidget(iPanel);
                        UniTimePageLabel.getInstance().setPageName(getPageName());
                    }
                } else if (modified != null && detail != null && detail.getId().equals(modified.getId())) {
                    LoadingWidget.execute(EventDetailRpcRequest.requestEventDetails(iSession.getAcademicSessionId(),
                            modified.getId()), new AsyncCallback<EventInterface>() {
                                @Override
                                public void onFailure(Throwable caught) {
                                    UniTimeNotifications.error(
                                            MESSAGES.failedLoad(detail.getName(), caught.getMessage()), caught);
                                }

                                @Override
                                public void onSuccess(EventInterface result) {
                                    LoadingWidget.getInstance().hide();
                                    if (result == null) {
                                        iRootPanel.setWidget(iPanel);
                                        UniTimePageLabel.getInstance().setPageName(getPageName());
                                    } else {
                                        iEventDetail.setEvent(result);
                                        iEventDetail.show();
                                    }
                                }
                            }, MESSAGES.waitLoading(detail.getName()));
                } else {
                    iRootPanel.setWidget(iPanel);
                    UniTimePageLabel.getInstance().setPageName(getPageName());
                }
                changeUrl();
            }

            @Override
            protected void onShow() {
                if (iTable != null)
                    iTable.clearHover();
                iRootPanel.setWidget(iEventAdd);
                changeUrl();
            }
        };

        iMeetingClickHandler = new MeetingClickHandler() {
            @Override
            public void onMeetingClick(final MeetingClickEvent event) {
                if (!event.getEvent().isCanView())
                    return;
                LoadingWidget.execute(EventDetailRpcRequest.requestEventDetails(iSession.getAcademicSessionId(),
                        event.getEvent().getId()), new AsyncCallback<EventInterface>() {
                            @Override
                            public void onFailure(Throwable caught) {
                                UniTimeNotifications.error(
                                        MESSAGES.failedLoad(event.getEvent().getName(), caught.getMessage()),
                                        caught);
                            }

                            @Override
                            public void onSuccess(EventInterface result) {
                                LoadingWidget.getInstance().hide();
                                iEventDetail.setEvent(result);
                                iEventDetail.show();
                            }
                        }, MESSAGES.waitLoading(event.getEvent().getName()));
            }
        };

        iTable = new EventMeetingTable(EventMeetingTable.Mode.ListOfEvents, true, this) {
            @Override
            protected void onSortByChanded(EventComparator.EventMeetingSortBy sortBy, boolean asc) {
                changeUrl();
            }

            @Override
            protected void onColumnShownOrHid(int eventCookieFlags) {
                changeUrl();
            }
        };
        iTable.setMeetingFilter(this);
        iTable.addMouseClickListener(new MouseClickListener<EventMeetingRow>() {
            @Override
            public void onMouseClick(final TableEvent<EventMeetingRow> event) {
                if (event.getData() == null)
                    return;
                final EventInterface e = event.getData().getEvent();
                if (e == null || !e.isCanView())
                    return;
                LoadingWidget.execute(
                        EventDetailRpcRequest.requestEventDetails(iSession.getAcademicSessionId(), e.getId()),
                        new AsyncCallback<EventInterface>() {
                            @Override
                            public void onFailure(Throwable caught) {
                                UniTimeNotifications.error(MESSAGES.failedLoad(e.getName(), caught.getMessage()),
                                        caught);
                            }

                            @Override
                            public void onSuccess(EventInterface result) {
                                LoadingWidget.getInstance().hide();
                                iEventDetail.setEvent(result);
                                iEventDetail.show();
                            }
                        }, MESSAGES.waitLoading(e.getName()));
            }
        });

        iApproveDialog = new ApproveDialog(this) {
            @Override
            protected void onSubmit(ApproveEventRpcRequest.Operation operation, List<EventMeetingRow> data,
                    String message, boolean emailConfirmation) {
                Map<EventInterface, List<MeetingInterface>> event2meetings = new HashMap<EventInterface, List<MeetingInterface>>();
                List<EventInterface> events = new ArrayList<EventInterface>();
                for (EventMeetingRow row : data) {
                    if (row.getMeeting() != null) {
                        List<MeetingInterface> meetings = event2meetings.get(row.getEvent());
                        if (meetings == null) {
                            meetings = new ArrayList<EventInterface.MeetingInterface>();
                            event2meetings.put(row.getEvent(), meetings);
                            events.add(row.getEvent());
                        }
                        meetings.add(row.getMeeting());
                    } else {
                        events.add(row.getEvent());
                    }
                }
                onSubmit(operation, events.iterator(), event2meetings, message, emailConfirmation,
                        new GwtRpcResponseList<EventInterface>(iData));
            }

            protected void onSubmit(final ApproveEventRpcRequest.Operation operation,
                    final Iterator<EventInterface> events,
                    final Map<EventInterface, List<MeetingInterface>> event2meetings, final String message,
                    final boolean emailConfirmation, final GwtRpcResponseList<EventInterface> data) {
                if (events.hasNext()) {
                    final EventInterface event = events.next();
                    List<MeetingInterface> meetings = event2meetings.get(event);
                    if (meetings == null) {
                        meetings = new ArrayList<MeetingInterface>();
                        for (MeetingInterface meeting : event.getMeetings()) {
                            switch (operation) {
                            case APPROVE:
                            case REJECT:
                                if (meeting.isCanApprove() && !filter(event, meeting))
                                    meetings.add(meeting);
                                break;
                            case CANCEL:
                                if (meeting.isCanCancel() && !filter(event, meeting))
                                    meetings.add(meeting);
                                break;
                            case INQUIRE:
                                if (meeting.isCanInquire() && !filter(event, meeting))
                                    meetings.add(meeting);
                                break;
                            }
                        }
                    }
                    if (meetings.isEmpty()) {
                        onSubmit(operation, events, event2meetings, message, emailConfirmation, data);
                    } else {
                        switch (operation) {
                        case APPROVE:
                            LoadingWidget.getInstance().show(MESSAGES.waitForApproval(event.getName()));
                            break;
                        case INQUIRE:
                            LoadingWidget.getInstance().show(MESSAGES.waitForInquiry(event.getName()));
                            break;
                        case REJECT:
                            LoadingWidget.getInstance().show(MESSAGES.waitForRejection(event.getName()));
                            break;
                        case CANCEL:
                            LoadingWidget.getInstance().show(MESSAGES.waitForCancellation(event.getName()));
                            break;
                        }
                        RPC.execute(
                                ApproveEventRpcRequest.createRequest(operation, iSession.getAcademicSessionId(),
                                        event, meetings, message, emailConfirmation),
                                new AsyncCallback<SaveOrApproveEventRpcResponse>() {
                                    @Override
                                    public void onFailure(Throwable caught) {
                                        LoadingWidget.getInstance().hide();
                                        UniTimeNotifications.error(caught);
                                        onSubmit(operation, events, event2meetings, message, emailConfirmation,
                                                data);
                                    }

                                    @Override
                                    public void onSuccess(SaveOrApproveEventRpcResponse result) {
                                        LoadingWidget.getInstance().hide();
                                        if (result.hasMessages())
                                            for (MessageInterface m : result.getMessages()) {
                                                if (m.isError())
                                                    UniTimeNotifications.warn(m.getMessage());
                                                else if (m.isWarning())
                                                    UniTimeNotifications.error(m.getMessage());
                                                else
                                                    UniTimeNotifications.info(m.getMessage());
                                            }
                                        switch (operation) {
                                        case APPROVE:
                                        case REJECT:
                                        case CANCEL:
                                            tinker(data, event.getId(), result.getEvent());
                                        }
                                        onSubmit(operation, events, event2meetings, message, emailConfirmation,
                                                data);
                                    }
                                });
                    }
                } else {
                    LoadingWidget.getInstance().hide();
                    populate(data);
                }
            }
        };
        iTable.setOperation(EventMeetingTable.OperationType.Approve, iApproveDialog);
        iTable.setOperation(EventMeetingTable.OperationType.Reject, iApproveDialog);
        iTable.setOperation(EventMeetingTable.OperationType.Inquire, iApproveDialog);
        iTable.setOperation(EventMeetingTable.OperationType.Cancel, iApproveDialog);

        History.addValueChangeHandler(new ValueChangeHandler<String>() {
            @Override
            public void onValueChange(ValueChangeEvent<String> event) {
                if (iInitialized) {
                    if ("event=add".equals(event.getValue()))
                        iHistoryToken.parse(event.getValue());
                    else
                        iHistoryToken.reset(event.getValue());
                    setup(false);
                    if (LoadingWidget.getInstance().isShowing())
                        LoadingWidget.getInstance().hide();
                }
            }
        });
    }

    private int getSelectedTab() {
        return iTabBar.getSelectedTab();
    }

    private void tabOrDataChanged(boolean keepSelection) {
        if (iTimeGrid != null)
            iTimeGrid.hideSelectionPopup();
        if (iTable != null)
            iTable.clearHover();
        if (getSelectedTab() == 0) {
            int nrDays = 4;
            int firstSlot = 84, lastSlot = 216;
            for (EventInterface event : iData) {
                if (event.getType() == EventType.Unavailabile
                        && !iEvents.hasChip(new Chip("type", "Not Available")))
                    continue;
                for (MeetingInterface meeting : event.getMeetings()) {
                    if (filterEvent(event, meeting))
                        continue;
                    if (meeting.getApprovalStatus() != ApprovalStatus.Pending
                            && meeting.getApprovalStatus() != ApprovalStatus.Approved)
                        continue;
                    if (firstSlot > meeting.getStartSlot())
                        firstSlot = meeting.getStartSlot();
                    if (lastSlot < meeting.getEndSlot())
                        lastSlot = meeting.getEndSlot();
                    nrDays = Math.max(nrDays, meeting.getDayOfWeek());
                }
            }
            Chip after = iEvents.getChip("after");
            if (after != null) {
                Integer slot = TimeUtils.parseTime(CONSTANTS, after.getValue(), null);
                if (slot != null && firstSlot > slot)
                    firstSlot = slot;
            }
            Chip before = iEvents.getChip("before");
            if (before != null) {
                Integer slot = TimeUtils.parseTime(CONSTANTS, before.getValue(), firstSlot);
                if (slot != null && lastSlot < slot)
                    lastSlot = slot;
            }
            nrDays++;
            int days[] = new int[nrDays];
            for (int i = 0; i < days.length; i++)
                days[i] = i;
            int firstHour = firstSlot / 12;
            int lastHour = 1 + (lastSlot - 1) / 12;
            if (firstHour <= 7 && firstHour > 0 && ((firstSlot % 12) <= 6))
                firstHour--;
            HashMap<Long, String> colors = new HashMap<Long, String>();
            List<SelectionInterface> selections = null;

            if (iTimeGrid != null) {
                if (keepSelection)
                    selections = iTimeGrid.getSelections();
                iTimeGrid.destroy();
            }
            iTimeGrid = new TimeGrid(colors, days, (int) (0.9 * ToolBox.getClientWidth() / nrDays), false, false,
                    (firstHour < 7 ? firstHour : 7), (lastHour > 18 ? lastHour : 18), this);
            iTimeGrid.addMeetingClickHandler(iMeetingClickHandler);

            iTimeGrid.clear();
            iTimeGrid.setResourceType(getResourceType());
            iTimeGrid.setSelectedWeeks(iWeekPanel.getSelected());
            iTimeGrid.setRoomResources(iRoomPanel.getSelected());
            if (selections != null)
                iTimeGrid.getSelections().addAll(selections);
            iTimeGrid.setMode(gridMode());
            int first = iWeekPanel.getFirstDayOfYear(), last = iWeekPanel.getLastDayOfYear();
            for (EventInterface event : sortedEvents()) {
                List<MeetingInterface> meetings = new ArrayList<MeetingInterface>();
                for (MeetingInterface meeting : event.getMeetings()) {
                    if (meeting.getApprovalStatus() != ApprovalStatus.Pending
                            && meeting.getApprovalStatus() != ApprovalStatus.Approved)
                        continue;
                    if (meeting.getMeetingDate() != null && !filterEvent(event, meeting)
                            && first <= meeting.getDayOfYear() && meeting.getDayOfYear() <= last)
                        meetings.add(meeting);
                }
                if (!meetings.isEmpty())
                    iTimeGrid.addEvent(event, meetings);
            }
            iTimeGrid.shrink(iEvents.hasChip(new FilterBox.Chip("day", null)),
                    iEvents.hasChip(new Chip("type", "Not Available")),
                    iEvents.hasChip(new FilterBox.Chip("day", CONSTANTS.longDays()[0])),
                    iEvents.hasChip(new FilterBox.Chip("day", CONSTANTS.longDays()[1])),
                    iEvents.hasChip(new FilterBox.Chip("day", CONSTANTS.longDays()[2])),
                    iEvents.hasChip(new FilterBox.Chip("day", CONSTANTS.longDays()[3])),
                    iEvents.hasChip(new FilterBox.Chip("day", CONSTANTS.longDays()[4])),
                    iEvents.hasChip(new FilterBox.Chip("day", CONSTANTS.longDays()[5])),
                    iEvents.hasChip(new FilterBox.Chip("day", CONSTANTS.longDays()[6])));
            List<WeekInterface> selected = iWeekPanel.getSelected();
            if (selected != null && !selected.isEmpty())
                iTimeGrid.labelDays(selected.get(0),
                        selected.size() == 1 ? null : selected.get(selected.size() - 1));
            iGridOrTablePanel.setWidget(iTimeGrid);
        } else {
            iTable.setMode(getSelectedTab() == 1 ? EventMeetingTable.Mode.ListOfEvents
                    : EventMeetingTable.Mode.ListOfMeetings);
            iTable.setEvents(iData);
            iGridOrTablePanel.setWidget(iTable);
        }
        iHeader.setHeaderTitle(name(getSelectedTab()));
        changeUrl();
    }

    private void setup(boolean init) {
        if (init)
            iInitialized = true;
        boolean reload = init;
        boolean isDefault = true;
        if (iHistoryToken.isChanged("events", "", iEvents.getValue())) {
            iEvents.setValue(iHistoryToken.getParameter("events", ""), true);
            reload = true;
            if (!iEvents.getValue().equals(""))
                isDefault = false;
        }
        if (iHistoryToken.isChanged("type", "room",
                iResourceTypes.getValue(iResourceTypes.getSelectedIndex()).toLowerCase())) {
            String typeString = iHistoryToken.getParameter("type", "room");
            if (typeString != null)
                for (int idx = 0; idx < iResourceTypes.getItemCount(); idx++) {
                    if (iResourceTypes.getValue(idx).equalsIgnoreCase(typeString)) {
                        iResourceTypes.setSelectedIndex(idx);
                    }
                }
            reload = true;
        }
        if (iHistoryToken.isChanged("rooms", (getResourceType() == ResourceType.ROOM ? "flag:Event" : ""),
                iRooms.getValue())) {
            iRooms.setValue(iHistoryToken.getParameter("rooms",
                    (getResourceType() == ResourceType.ROOM ? "flag:Event" : "")), true);
            reload = true;
            if (!iRooms.getValue().equals(getResourceType() == ResourceType.ROOM ? "flag:Event" : "")) {
                isDefault = false;
            }
        }
        if (iHistoryToken.isChanged("name", "", iResources.getText())) {
            iResources.setText(iHistoryToken.getParameter("name", ""));
            reload = (getResourceType() != ResourceType.ROOM);
            if (!iResources.getText().equals(""))
                isDefault = false;
        }
        UniTimePageLabel.getInstance().setPageName(getPageName());
        if (iHistoryToken.isChanged("tab", "0", String.valueOf(getSelectedTab()))) {
            iTabBar.selectTab(Integer.parseInt(iHistoryToken.getParameter("tab", "0")), isShowingResults());
        }
        boolean fireWeekPanel = false, fireRoomPanel = false;
        if (iHistoryToken.isChanged("date", iLocDate)) {
            iLocDate = iHistoryToken.getParameter("date");
            if (iWeekPanel.hasValues()) {
                iWeekPanel.setValue(iWeekPanel.parse(iLocDate));
                iWeekPanel.setFilterEnabled(!iMatchingWeeks.isEmpty());
                fireWeekPanel = isShowingResults();
            }
        }
        if (iHistoryToken.isChanged("room", iLocRoom)) {
            iLocRoom = iHistoryToken.getParameter("room");
            if (iRoomPanel.hasValues()) {
                iRoomPanel.setFilterEnabled(!iMatchingRooms.isEmpty());
                iRoomPanel.setValue(iRoomPanel.parse(iLocRoom));
                fireRoomPanel = isShowingResults();
            }
        }
        if (iHistoryToken.hasParameter("sort"))
            iTable.setSortBy(iHistoryToken.getParameter("sort"));
        if (iHistoryToken.hasParameter("event")) {
            if (iHistoryToken.isChanged("term", iSession.getAcademicSessionAbbreviation())
                    && iHistoryToken.getParameter("term") != null)
                iSession.selectSession(iHistoryToken.getParameter("term"), null);
            if ("add".equals(iHistoryToken.getParameter("event"))) {
                iEventAdd.setEvent(null);
                iEventAdd.show();
            } else {
                Long eventId = Long.valueOf(iHistoryToken.getParameter("event"));
                LoadingWidget.execute(
                        EventDetailRpcRequest.requestEventDetails(iSession.getAcademicSessionId(), eventId),
                        new AsyncCallback<EventInterface>() {
                            @Override
                            public void onFailure(Throwable caught) {
                                UniTimeNotifications.error(
                                        MESSAGES.failedLoad(MESSAGES.anEvent(), caught.getMessage()), caught);
                            }

                            @Override
                            public void onSuccess(EventInterface result) {
                                LoadingWidget.getInstance().hide();
                                iEventDetail.setEvent(result);
                                iEventDetail.show();
                            }
                        }, MESSAGES.waitLoading(MESSAGES.anEvent()));
            }
        } else {
            iRootPanel.setWidget(iPanel);
            if (iHistoryToken.hasParameter("term")
                    && iHistoryToken.isChanged("term", iSession.getAcademicSessionAbbreviation())) {
                iSession.selectSession(iHistoryToken.getParameter("term"), null);
            } else if (reload && iProperties != null) {
                resourceTypeChanged(isShowingResults() || (!isDefault && CONSTANTS.searchWhenPageIsLoaded()));
            }
        }
        if (fireWeekPanel)
            ValueChangeEvent.fire(iWeekPanel, iWeekPanel.getValue());
        if (fireRoomPanel)
            ValueChangeEvent.fire(iRoomPanel, iRoomPanel.getValue());
    }

    private Collection<EventInterface> sortedEvents() {
        TreeSet<EventInterface> data = new TreeSet<EventInterface>(new Comparator<EventInterface>() {
            @Override
            public int compare(EventInterface e1, EventInterface e2) {
                int cmp = new Integer(e1.getMeetings().first().getDayOfYear())
                        .compareTo(e2.getMeetings().first().getDayOfYear());
                if (cmp != 0)
                    return cmp;
                cmp = new Integer(e1.getMeetings().first().getStartSlot())
                        .compareTo(e2.getMeetings().first().getStartSlot());
                if (cmp != 0)
                    return cmp;
                return e1.compareTo(e2);
            }
        });
        data.addAll(iData);
        return data;
    }

    public ResourceType getResourceType() {
        if (iResourceTypes.getSelectedIndex() < 0)
            return null;
        return ResourceType.valueOf(iResourceTypes.getValue(iResourceTypes.getSelectedIndex()));
    }

    public String getResourceName() {
        if (getResourceType() == ResourceType.PERSON && (iProperties == null || !iProperties.isCanLookupPeople()))
            return "";
        return (iResources.getText() == null || iResources.getText().isEmpty() ? null : iResources.getText());
    }

    private void resourceTypeChanged(boolean loadData) {
        ResourceType type = getResourceType();
        if (type == null) {
            UniTimeNotifications.warn(MESSAGES.warnNoResourceType());
            return;
        }
        if (type == ResourceType.ROOM) {
            iFilter.getRowFormatter().setVisible(iResourcesRow, false);
            iFilterHeader.setEnabled("lookup", false);
            if (loadData) {
                if (iSession.getAcademicSessionId() == null) {
                    UniTimeNotifications.warn(MESSAGES.warnNoSession());
                } else if (iProperties == null) {
                    UniTimeNotifications.warn(MESSAGES.warnNoEventProperties(iSession.getAcademicSessionName()));
                } else {
                    ResourceInterface resource = new ResourceInterface();
                    resource.setType(ResourceType.ROOM);
                    resource.setName(iRooms.getValue());
                    resourceChanged(resource);
                }
            }
        } else {
            iFilter.getRowFormatter().setVisible(iResourcesRow, type != ResourceType.PERSON);
            ((Label) iFilter.getWidget(iResourcesRow, 0)).setText(type.getName(CONSTANTS) + ":");
            iFilterHeader.setEnabled("lookup", iProperties != null && iProperties.isCanLookupPeople()
                    && getResourceType() == ResourceType.PERSON);
            if (getResourceName() != null || (type == ResourceType.PERSON && loadData)) {
                if (iSession.getAcademicSessionId() == null) {
                    UniTimeNotifications.warn(MESSAGES.warnNoSession());
                } else if (iProperties == null) {
                    UniTimeNotifications.warn(MESSAGES.warnNoEventProperties(iSession.getAcademicSessionName()));
                } else {
                    LoadingWidget.execute(ResourceLookupRpcRequest.findResource(iSession.getAcademicSessionId(),
                            type, getResourceName()), new AsyncCallback<GwtRpcResponseList<ResourceInterface>>() {
                                @Override
                                public void onFailure(Throwable caught) {
                                    UniTimeNotifications.error(
                                            MESSAGES.failedLoad(getResourceName(), caught.getMessage()), caught);
                                    hideResults();
                                }

                                @Override
                                public void onSuccess(GwtRpcResponseList<ResourceInterface> result) {
                                    resourceChanged(result.get(0));
                                }
                            }, MESSAGES.waitLoading(type.getName(CONSTANTS)
                                    + (type != ResourceType.PERSON ? " " + getResourceName() : "")));
                }
            } else if (getResourceName() == null && loadData) {
                UniTimeNotifications.warn(MESSAGES
                        .warnNoResourceName(CONSTANTS.resourceName()[getResourceType().ordinal()].toLowerCase()));
                iResources.setFocus(true);
            }
        }
        if (!loadData) {
            iData = null;
            hideResults();
        }
    }

    private GwtRpcResponseList<EventInterface> tinker(GwtRpcResponseList<EventInterface> data, Long oldEventId,
            EventInterface newEvent) {
        for (Iterator<EventInterface> i = data.iterator(); i.hasNext();) {
            EventInterface event = i.next();

            if (event.getId().equals(oldEventId)) {
                i.remove();
                continue;
            } else if (event.hasConflicts()) {
                for (Iterator<EventInterface> j = event.getConflicts().iterator(); j.hasNext();)
                    if (j.next().getId().equals(oldEventId))
                        j.remove();
            }

            if (newEvent != null && newEvent.getId() != null && event.inConflict(newEvent)) {
                event.addConflict(event.createConflictingEvent(newEvent));
            }
        }

        if (newEvent != null && newEvent.getId() != null)
            data.add(newEvent);

        return data;
    }

    private void resourceChanged(final ResourceInterface resource) {
        iResource = resource;
        LoadingWidget.execute(iRooms.getElementsRequest(), new AsyncCallback<FilterRpcResponse>() {
            @Override
            public void onFailure(Throwable caught) {
                UniTimeNotifications.error(MESSAGES
                        .failedLoad(resource.getType() == ResourceType.ROOM ? MESSAGES.resourceRoom().toLowerCase()
                                : resource.getName(), caught.getMessage()),
                        caught);
                hideResults();
            }

            @Override
            public void onSuccess(FilterRpcResponse result) {
                if (result == null)
                    return;
                List<ResourceInterface> rooms = new ArrayList<ResourceInterface>();
                if (result.hasResults())
                    for (FilterRpcResponse.Entity room : result.getResults()) {
                        rooms.add(new ResourceInterface(room));
                    }
                Collections.sort(rooms);
                iRoomPanel.setValues(rooms);

                if (!result.hasResults() || result.getResults().isEmpty()) {
                    UniTimeNotifications.error(MESSAGES.errorNoMatchingRooms());
                    hideResults();
                    return;
                }

                LoadingWidget.execute(
                        EventLookupRpcRequest.findEvents(iSession.getAcademicSessionId(), iResource,
                                iEvents.getElementsRequest(), iRooms.getElementsRequest(), CONSTANTS.maxMeetings()),
                        new AsyncCallback<GwtRpcResponseList<EventInterface>>() {
                            @Override
                            public void onSuccess(GwtRpcResponseList<EventInterface> result) {
                                if (result != null)
                                    populate(result);
                                iApproveDialog.reset(iProperties);
                            }

                            @Override
                            public void onFailure(Throwable caught) {
                                UniTimeNotifications
                                        .error(MESSAGES.failedLoad(resource.getType() == ResourceType.ROOM
                                                ? MESSAGES.resourceRoom().toLowerCase()
                                                : resource.getName(), caught.getMessage()), caught);
                                hideResults();
                            }
                        },
                        MESSAGES.waitLoadingTimetable(
                                resource.getType() == ResourceType.ROOM ? MESSAGES.resourceRoom().toLowerCase()
                                        : resource.getName(),
                                iSession.getAcademicSessionName()));
            }

        }, MESSAGES.waitLoadingTimetable(
                resource.getType() == ResourceType.ROOM ? MESSAGES.resourceRoom().toLowerCase()
                        : resource.getName(),
                iSession.getAcademicSessionName()));
    }

    private void populate(GwtRpcResponseList<EventInterface> result) {
        iData = result;
        if (iData.isEmpty())
            UniTimeNotifications.warn(MESSAGES.failedNoEvents());

        Collections.sort(iData);

        iMatchingRooms.clear();
        int nrMeetings = 0;
        for (EventInterface event : iData) {
            for (MeetingInterface meeting : event.getMeetings()) {
                if (meeting.getLocation() != null)
                    iMatchingRooms.add(meeting.getLocation().getId());
            }
            nrMeetings += event.getMeetings().size();
        }
        iRoomPanel.setValue(iRoomPanel.parse(iLocRoom));
        iRoomPanel.setFilterEnabled(!iMatchingRooms.isEmpty());

        iMatchingWeeks.clear();
        for (WeekInterface week : iWeekPanel.getValues()) {
            boolean hasEvents = false;
            events: for (EventInterface event : iData) {
                for (MeetingInterface meeting : event.getMeetings()) {
                    if (meeting.getDayOfYear() >= week.getDayOfYear()
                            && meeting.getDayOfYear() < week.getDayOfYear() + 7) {
                        hasEvents = true;
                        break events;
                    }
                }
            }
            if (hasEvents)
                iMatchingWeeks.add(week.getDayOfYear());
        }
        iWeekPanel.setValue(iWeekPanel.parse(iLocDate));
        iWeekPanel.setFilterEnabled(!iMatchingWeeks.isEmpty());

        if (nrMeetings > CONSTANTS.maxMeetings())
            iHeader.setErrorMessage(MESSAGES.warnTooManyMeetings(CONSTANTS.maxMeetings()));
        else
            iHeader.clearMessage();

        tabOrDataChanged(false);

        showResults();

        changeUrl();
    }

    private String name(int tab) {
        String resource;
        if (iResource.getType() == ResourceType.ROOM) {
            if (iRoomPanel.getValue() != null && iRoomPanel.getValue().isOne()) {
                resource = iRoomPanel.getValue().getFirst().getName();
            } else if (iRoomPanel.getValues() != null && iRoomPanel.getValues().size() == 1) {
                resource = iRoomPanel.getValues().get(0).getName();
            } else {
                resource = MESSAGES.resourceRoom();
            }
        } else {
            resource = iResource.getNameWithHint();
        }
        String session = iSession.getAcademicSessionName();
        if (iWeekPanel.getValue() != null && !iWeekPanel.getValue().isAll())
            session = iWeekPanel.getValue().toString().toLowerCase();
        switch (tab) {
        case 0:
            return MESSAGES.sectTimetable(resource, session);
        case 1:
            return MESSAGES.sectEventList(resource, session);
        case 2:
            return MESSAGES.sectMeetingList(resource, session);
        }
        return null;
    }

    @Override
    public boolean filter(EventInterface event, MeetingInterface meeting) {
        // if (event != null && event.getId() == null) return true;
        if (event != null && event.getType() == EventType.Unavailabile
                && !iEvents.hasChip(new Chip("type", "Not Available")))
            return true;
        return filterEvent(event, meeting);
    }

    public boolean filterEvent(EventInterface event, MeetingInterface meeting) {
        if (event != null && event.getType() == EventType.Unavailabile
                && !iEvents.hasChip(new Chip("type", "Not Available"))) {
            if (getResourceType() != ResourceType.ROOM || gridMode() != TimeGrid.Mode.OVERLAP)
                return true;
        }
        if (iWeekPanel.getValue() != null && !iWeekPanel.getValue().isAll()) {
            int firstDayOfYear = iWeekPanel.getValue().getFirst().getDayOfYear();
            int lastDayOfYear = (iWeekPanel.getValue().isOne() ? iWeekPanel.getValue().getFirst()
                    : iWeekPanel.getValue().getLast()).getDayOfYear() + 6;
            if (meeting.getDayOfYear() < firstDayOfYear || meeting.getDayOfYear() > lastDayOfYear)
                return true;
        }
        if (iRoomPanel.getValue() != null && !iRoomPanel.getValue().isAll()) {
            if (iRoomPanel.getValue().isOne()) {
                if (!iRoomPanel.getValue().getFirst().getName().equals(meeting.getLocationName()))
                    return true;
            } else {
                if (iRoomPanel.getValue().getFirst().getName().compareTo(meeting.getLocationName()) > 0)
                    return true;
                if (iRoomPanel.getValue().getLast().getName().compareTo(meeting.getLocationName()) < 0)
                    return true;
            }
        }

        return false;
    }

    private TimeGrid.Mode gridMode() {
        if ("0".equals(iHistoryToken.getParameter("eq")))
            return TimeGrid.Mode.FILLSPACE;
        switch (iResource.getType()) {
        case ROOM:
            return (iRoomPanel.isOne() || (iWeekPanel.isOne() && iRoomPanel.getSelected().size() <= 20)
                    ? TimeGrid.Mode.OVERLAP
                    : TimeGrid.Mode.PROPORTIONAL);
        case PERSON:
            return TimeGrid.Mode.OVERLAP;
        case GROUP:
            if (CONSTANTS.timeGridStudentGroupDoesNotOverlap())
                return TimeGrid.Mode.OVERLAP;
            // else use default
        default:
            return (iRoomPanel.isOne() || (iWeekPanel.isOne() && iRoomPanel.getSelected().size() <= 10)
                    ? TimeGrid.Mode.OVERLAP
                    : TimeGrid.Mode.PROPORTIONAL);
        }
    }

    public static class ResourceSuggestion implements Suggestion {
        private ResourceInterface iResource;

        public ResourceSuggestion(ResourceInterface resource) {
            iResource = resource;
        }

        public ResourceInterface getResource() {
            return iResource;
        }

        @Override
        public String getDisplayString() {
            if (iResource.hasTitle())
                return iResource.getTitle();
            if (iResource.hasAbbreviation() && !iResource.getAbbreviation().equals(iResource.getName()))
                return iResource.getAbbreviation() + " - " + iResource.getName();
            return (iResource.hasAbbreviation() ? iResource.getAbbreviation() : iResource.getName());
        }

        @Override
        public String getReplacementString() {
            return (iResource.hasAbbreviation() ? iResource.getAbbreviation() : iResource.getName());
        }
    }

    protected String query(String extra, boolean includeRoomWeekFilter) {
        String query = "sid=" + iSession.getAcademicSessionId()
                + (iResource == null || iResource.getType() == null ? ""
                        : "&type=" + iResource.getType().toString().toLowerCase())
                + (iResource == null || iResource.getId() == null ? "" : "&id=" + iResource.getId())
                + (iResource == null || iResource.getExternalId() == null ? ""
                        : "&ext=" + iResource.getExternalId());

        FilterRpcRequest events = iEvents.getElementsRequest();
        if (includeRoomWeekFilter) {
            if (iWeekPanel.getValue() != null && !iWeekPanel.getValue().isAll()) {
                events.setOption("from", String.valueOf(iWeekPanel.getValue().getFirst().getDayOfYear()));
                events.setOption("to",
                        String.valueOf((iWeekPanel.getValue().isOne() ? iWeekPanel.getValue().getFirst()
                                : iWeekPanel.getValue().getLast()).getDayOfYear() + 6));
            }
            if (iRoomPanel.getValue() != null && !iRoomPanel.getValue().isAll()) {
                for (ResourceInterface resource : iRoomPanel.getSelected())
                    events.addOption("room", resource.getId().toString());
            } else {
                FilterRpcRequest rooms = iRooms.getElementsRequest();
                if (rooms.hasOptions()) {
                    for (Map.Entry<String, Set<String>> option : rooms.getOptions().entrySet()) {
                        for (String value : option.getValue()) {
                            query += "&r:" + option.getKey() + "=" + URL.encodeQueryString(value);
                        }
                    }
                }
                if (rooms.getText() != null && !rooms.getText().isEmpty()) {
                    query += "&r:text=" + URL.encodeQueryString(rooms.getText());
                }
            }
        } else {
            FilterRpcRequest rooms = iRooms.getElementsRequest();
            if (rooms.hasOptions()) {
                for (Map.Entry<String, Set<String>> option : rooms.getOptions().entrySet()) {
                    for (String value : option.getValue()) {
                        query += "&r:" + option.getKey() + "=" + URL.encodeQueryString(value);
                    }
                }
            }
            if (rooms.getText() != null && !rooms.getText().isEmpty()) {
                query += "&r:text=" + URL.encodeQueryString(rooms.getText());
            }
        }
        if (events.hasOptions()) {
            for (Map.Entry<String, Set<String>> option : events.getOptions().entrySet()) {
                for (String value : option.getValue()) {
                    query += "&e:" + option.getKey() + "=" + URL.encodeQueryString(value);
                }
            }
        }
        if (events.getText() != null && !events.getText().isEmpty()) {
            query += "&e:text=" + URL.encodeQueryString(events.getText());
        }

        if (iTable.hasSortBy())
            query += "&sort=" + iTable.getSortBy();

        if (extra != null && !extra.isEmpty())
            query += "&" + extra;

        if (iEvents.hasChip(new Chip("type", "Not Available"))
                || (getResourceType() == ResourceType.ROOM && gridMode() == TimeGrid.Mode.OVERLAP))
            query += "&ua=1";

        return query;
    }

    protected void export(String format, boolean includeRoomWeekFilter) {
        RPC.execute(EncodeQueryRpcRequest.encode(query(format, includeRoomWeekFilter)),
                new AsyncCallback<EncodeQueryRpcResponse>() {
                    @Override
                    public void onFailure(Throwable caught) {
                    }

                    @Override
                    public void onSuccess(EncodeQueryRpcResponse result) {
                        ToolBox.open(GWT.getHostPageBaseURL() + "export?q=" + result.getQuery());
                    }
                });
    }

    protected void copyToClipboard(String format, final boolean personal) {
        RPC.execute(EncodeQueryRpcRequest.encode(query(format, false), true),
                new AsyncCallback<EncodeQueryRpcResponse>() {
                    @Override
                    public void onFailure(Throwable caught) {
                    }

                    @Override
                    public void onSuccess(final EncodeQueryRpcResponse result) {
                        final UniTimeDialogBox dialog = new UniTimeDialogBox(true, false);
                        dialog.setText(MESSAGES.opExportICalendar());
                        dialog.setEscapeToHide(true);
                        SimpleForm form = new SimpleForm();
                        HTML description = null;
                        if (personal)
                            description = new HTML(MESSAGES.exportICalendarDescriptionPersonal(), true);
                        else
                            description = new HTML(
                                    MESSAGES.exportICalendarDescriptionOther(iSession.getAcademicSessionName()),
                                    true);
                        description.getElement().getStyle().setProperty("max-width", "500px");
                        form.addRow(description);
                        final TextArea ta = new TextArea();
                        ta.setStyleName("unitime-TextArea");
                        ta.setVisibleLines(5);
                        ta.setCharacterWidth(80);
                        ta.setText(GWT.getHostPageBaseURL() + (result.hasHash() ? "export?x=" + result.getHash()
                                : "export?q=" + result.getQuery()));
                        UniTimeWidget<TextArea> w = new UniTimeWidget<TextArea>(ta);
                        w.setHint(MESSAGES.hintCtrlCToCopy());
                        form.addRow(w);
                        w.getElement().getStyle().setMarginBottom(5, Unit.PX);

                        HTML message = new HTML(MESSAGES.exportICalendarDownload(MESSAGES.buttonDownload()), true);
                        message.getElement().getStyle().setProperty("max-width", "500px");
                        form.addRow(message);

                        UniTimeHeaderPanel h = new UniTimeHeaderPanel();
                        h.addButton("download", MESSAGES.buttonDownload(), new ClickHandler() {
                            @Override
                            public void onClick(ClickEvent event) {
                                ToolBox.open(GWT.getHostPageBaseURL() + "export?q=" + result.getQuery());
                                dialog.hide();
                            }
                        });
                        h.addButton("close", MESSAGES.buttonClose(), new ClickHandler() {
                            @Override
                            public void onClick(ClickEvent event) {
                                dialog.hide();
                            }
                        });

                        form.addBottomRow(h);
                        dialog.setWidget(form);
                        Scheduler.get().scheduleDeferred(new Command() {
                            @Override
                            public void execute() {
                                ta.setFocus(true);
                                ta.selectAll();
                            }
                        });
                        dialog.center();
                    }
                });
    }

    private native static void nativeCopyToClipboard(Element e)/*-{
                                                               alert('here');
                                                               e.focus();
                                                               e.select();
                                                               $doc.execCommand('Copy');
                                                               alert('Copied');
                                                               }-*/;

    protected void changeUrl() {
        if (iTimeGrid != null) {
            RPC.execute(EncodeQueryRpcRequest.encode(query("output=events.ics", true)),
                    new AsyncCallback<EncodeQueryRpcResponse>() {
                        @Override
                        public void onFailure(Throwable caught) {
                            iHeader.setEnabled("export", false);
                            iHeader.setEnabled("operations", false);
                        }

                        @Override
                        public void onSuccess(EncodeQueryRpcResponse result) {
                            iHeader.setEnabled("export", true);
                            iHeader.setEnabled("operations", getSelectedTab() > 0);
                        }
                    });
        }

        iHistoryToken.reset(null);
        iHistoryToken.setParameter("term", iSession.getAcademicSessionAbbreviation());
        iHistoryToken.setParameter("type",
                iResourceTypes.getValue(iResourceTypes.getSelectedIndex()).toLowerCase());
        if (iResource != null && iResource.getAbbreviation() != null && iResource.getType() != ResourceType.PERSON)
            iHistoryToken.setParameter("name", iResource.getAbbreviation());
        if (iLocDate != null)
            iHistoryToken.setParameter("date", iLocDate);
        iHistoryToken.setParameter("events", iEvents.getValue().trim());
        iHistoryToken.setParameter("rooms", iRooms.getValue().trim());
        if (iLocRoom != null)
            iHistoryToken.setParameter("room", iLocRoom);
        iHistoryToken.setParameter("tab", String.valueOf(getSelectedTab()));
        if (iEventDetail.equals(iRootPanel.getWidget()))
            iHistoryToken.setParameter("event", iEventDetail.getEvent().getId());
        else if (iEventAdd.equals(iRootPanel.getWidget())) {
            Long id = iEventAdd.getEventId();
            iHistoryToken.setParameter("event", id == null ? "add" : id.toString());
        }
        if (iTable.hasSortBy())
            iHistoryToken.setParameter("sort", iTable.getSortBy());
        iHistoryToken.mark();
        Client.fireGwtPageChanged(new Client.GwtPageChangeEvent());
    }

    private class RoomSelector extends IntervalSelector<ResourceInterface> {
        public RoomSelector() {
            super(true);
            addValueChangeHandler(new ValueChangeHandler<Interval>() {
                @Override
                public void onValueChange(ValueChangeEvent<Interval> event) {
                    setAriaLabel(ARIA.roomSelection(toAriaString()));
                }
            });

            addFocusHandler(new FocusHandler() {
                @Override
                public void onFocus(FocusEvent event) {
                    setAriaLabel(ARIA.roomSelection(toAriaString()));
                }
            });
        }

        @Override
        public Interval parse(String query) {
            if (query == null || query.isEmpty() || getValues() == null)
                return new Interval();

            Interval ret = null;
            for (int i = 0; i < getValues().size(); i++) {
                ResourceInterface first = getValues().get(i);
                if (query.equalsIgnoreCase(first.getName()))
                    return new Interval(first);
                for (int j = i + 1; j < getValues().size(); j++) {
                    ResourceInterface last = getValues().get(j);
                    if (query.equalsIgnoreCase(first.getName() + "-" + last.getName())
                            || query.equalsIgnoreCase(first.getName() + " - " + last.getName()))
                        return new Interval(first, last);
                    if (filter(first) || filter(last))
                        continue;
                    if (query.toLowerCase().startsWith(first.getName().toLowerCase())
                            && ((first.getName() + "-" + last.getName()).toLowerCase()
                                    .startsWith(query.toLowerCase())
                                    || (first.getName() + " - " + last.getName()).toLowerCase()
                                            .startsWith(query.toLowerCase())))
                        ret = new Interval(first, last);
                    if (query.toLowerCase().startsWith(first.getName().toLowerCase())
                            && last.getName().indexOf(' ') >= 0) {
                        String building = last.getName().substring(0, last.getName().lastIndexOf(' '));
                        String room = last.getName().substring(last.getName().lastIndexOf(' ') + 1);
                        if (first.getName().startsWith(building + " ") && ((first.getName() + "-" + room)
                                .toLowerCase().startsWith(query.toLowerCase())
                                || (first.getName() + " - " + room).toLowerCase().startsWith(query.toLowerCase())))
                            ret = new Interval(first, last);
                    }
                }
            }
            if (ret != null)
                return ret;

            ResourceInterface first = null, last = null;
            for (ResourceInterface e : getValues()) {
                if (isFilterEnabled() && filter(e))
                    continue;
                if (e.getName().equalsIgnoreCase(query))
                    return new Interval(e);
                if (e.getName().toLowerCase().startsWith(query.toLowerCase())) {
                    if (first == null)
                        first = e;
                    last = e;
                } else {
                    if (first != null)
                        return new Interval(first, last);
                }
            }
            if (first != null)
                return new Interval(first, last);

            return new Interval();
        }

        @Override
        protected String getDisplayString(Interval interval) {
            if (interval == null || interval.isAll()) {
                return interval.isEnableFilter() ? MESSAGES.itemAllRoomsWithFilter() : MESSAGES.itemAllRooms();
            }
            if (interval.isOne()) {
                String hint = interval.getFirst().getRoomType()
                        + (interval.getFirst().hasSize()
                                ? ", " + MESSAGES.hintRoomCapacity("" + interval.getFirst().getSize())
                                : "")
                        + (interval.getFirst().hasDistance()
                                ? ", " + MESSAGES
                                        .hintRoomDistance("" + Math.round(interval.getFirst().getDistance()))
                                : "");
                return interval.getFirst().getName() + " <span class='item-hint'>" + hint + "</span>";
            } else {
                return "&nbsp;&nbsp;&nbsp;" + interval.getFirst().getName() + " - " + interval.getLast().getName();
            }
        }

        @Override
        protected String getReplaceString(Interval interval) {
            if (interval == null || interval.isAll())
                return interval.isEnableFilter() ? MESSAGES.itemAllRoomsWithFilter() : MESSAGES.itemAllRooms();
            return interval.isOne() ? interval.getFirst().getName()
                    : interval.getFirst().getName() + " - " + interval.getLast().getName();

        }

        public String getSelection() {
            if (getValue() == null || getValue().isAll())
                return "";
            return getValue().isOne() ? getValue().getFirst().getName()
                    : getValue().getFirst().getName() + "-" + getValue().getLast().getName();
        }

        @Override
        public void setValue(Interval value, boolean fireEvents) {
            if (value != null && ((value.getFirst() != null && filter(value.getFirst()))
                    || (value.getLast() != null && filter(value.getLast()))))
                setFilterEnabled(false);
            super.setValue(value, fireEvents);
        }
    }

    private void hideResults() {
        for (int i = 1; i < iPanel.getRowCount(); i++)
            iPanel.getRowFormatter().setVisible(i, false);
        // iGridOrTablePanel.setVisible(false);
        // iTabBar.setVisible(false);
        iHeader.setEnabled("print", false);
        iHeader.setEnabled("export", false);
        iHeader.setEnabled("operations", false);
    }

    private void showResults() {
        for (int i = 1; i < iPanel.getRowCount(); i++)
            iPanel.getRowFormatter().setVisible(i, true);
        iHeader.setEnabled("print", true);
        iHeader.setEnabled("export", iTable.getRowCount() > 1);
        iHeader.setEnabled("operations", getSelectedTab() > 0 && iTable.getRowCount() > 1);
        // iGridOrTablePanel.setVisible(true);
        // iTabBar.setVisible(true);
    }

    private boolean isShowingResults() {
        return iPanel.equals(iRootPanel.getWidget()) && iPanel.getRowFormatter().isVisible(1);
    }

    private void loadProperties(final AsyncCallback<EventPropertiesRpcResponse> callback) {
        iProperties = null;
        iSessionMonths = null;
        iTable.setShowMainContact(false);
        iFilterHeader.setEnabled("lookup", false);
        iFilterHeader.setEnabled("add", false);
        iFooter.setEnabled("add", false);
        if (iSession.getAcademicSessionId() != null) {
            RPC.execute(
                    EventPropertiesRpcRequest.requestEventProperties(iSession.getAcademicSessionId(), iType.name()),
                    new AsyncCallback<EventPropertiesRpcResponse>() {
                        @Override
                        public void onFailure(Throwable caught) {
                            UniTimeNotifications.error(
                                    MESSAGES.failedLoad(iSession.getAcademicSessionName(), caught.getMessage()),
                                    caught);
                            if (callback != null)
                                callback.onFailure(caught);
                        }

                        @Override
                        public void onSuccess(final EventPropertiesRpcResponse result) {
                            if (result.isCanSaveFilterDefaults()
                                    && "true".equals(iHistoryToken.getParameter("showFilter", "true"))) {
                                iHistoryToken.setDefaultParameter("events", result.getFilterDefault("events"));
                                iEvents.setDefaultValueProvider(new TakesValue<String>() {
                                    @Override
                                    public void setValue(final String value) {
                                        RPC.execute(new SaveFilterDefaultRpcRequest(iType.name() + ".events",
                                                iEvents.getValue()), new AsyncCallback<GwtRpcResponse>() {
                                                    @Override
                                                    public void onFailure(Throwable caught) {
                                                        UniTimeNotifications.error(
                                                                MESSAGES.failedSaveAsDefault(caught.getMessage()),
                                                                caught);
                                                    }

                                                    @Override
                                                    public void onSuccess(GwtRpcResponse result) {
                                                        iHistoryToken.setDefaultParameter("events", value);
                                                    }
                                                });
                                    }

                                    @Override
                                    public String getValue() {
                                        return iHistoryToken.getDefaultParameter("events", "");
                                    }
                                });
                                iHistoryToken.setDefaultParameter("rooms", result.getFilterDefault("rooms"));
                                iRooms.setDefaultValueProvider(new TakesValue<String>() {
                                    @Override
                                    public void setValue(final String value) {
                                        RPC.execute(new SaveFilterDefaultRpcRequest(iType.name() + ".rooms",
                                                iRooms.getValue()), new AsyncCallback<GwtRpcResponse>() {
                                                    @Override
                                                    public void onFailure(Throwable caught) {
                                                        UniTimeNotifications.error(
                                                                MESSAGES.failedSaveAsDefault(caught.getMessage()),
                                                                caught);
                                                    }

                                                    @Override
                                                    public void onSuccess(GwtRpcResponse result) {
                                                        iHistoryToken.setDefaultParameter("rooms", value);
                                                    }
                                                });
                                    }

                                    @Override
                                    public String getValue() {
                                        return iHistoryToken.getDefaultParameter("rooms", "");
                                    }
                                });
                            }
                            RPC.execute(new RequestSessionDetails(iSession.getAcademicSessionId()),
                                    new AsyncCallback<GwtRpcResponseList<SessionMonth>>() {
                                        @Override
                                        public void onFailure(Throwable caught) {
                                            UniTimeNotifications
                                                    .error(MESSAGES.failedLoad(iSession.getAcademicSessionName(),
                                                            caught.getMessage()), caught);
                                            if (callback != null)
                                                callback.onFailure(caught);
                                        }

                                        @Override
                                        public void onSuccess(GwtRpcResponseList<SessionMonth> months) {
                                            iProperties = result;
                                            iSessionMonths = months;
                                            iEvents.setOtherVisible(
                                                    result.isCanLookupMainContact() || result.isCanLookupPeople());
                                            iFilterHeader.setEnabled("lookup", result.isCanLookupPeople()
                                                    && getResourceType() == ResourceType.PERSON);
                                            iFilterHeader.setEnabled("add", result.isCanAddEvent() && "true"
                                                    .equals(iHistoryToken.getParameter("addEvent", "true")));
                                            iFooter.setEnabled("add", result.isCanAddEvent() && "true"
                                                    .equals(iHistoryToken.getParameter("addEvent", "true")));
                                            iEventAdd.setup(result);
                                            iTable.setShowMainContact(result.isCanLookupMainContact());
                                            iApproveDialog.reset(result);
                                            if (callback != null)
                                                callback.onSuccess(result);
                                        }
                                    });
                        }
                    });
            iLookup.setOptions("mustHaveExternalId,session=" + iSession.getAcademicSessionId());

        } else {
            iLookup.setOptions("mustHaveExternalId");
        }
    }

    @Override
    public Long getSessionId() {
        return iSession.getAcademicSessionId();
    }

    @Override
    public EventPropertiesRpcResponse getProperties() {
        return iProperties;
    }

    @Override
    public List<SelectionInterface> getSelection() {
        return (iTimeGrid == null ? null : iTimeGrid.getSelections());
    }

    @Override
    public String getRoomFilter() {
        return iRooms.getValue();
    }

    @Override
    public List<Date> getSelectedDates() {
        return null;
    }

    @Override
    public StartEndTime getSelectedTime() {
        return null;
    }

    @Override
    public ContactInterface getMainContact() {
        return iLookup.getValue() != null ? new EventInterface.ContactInterface(iLookup.getValue())
                : iProperties == null ? null : iProperties.getMainContact();
    }

    @Override
    public boolean isTooEarly(int startSlot, int endSlot) {
        if (iProperties == null || !iProperties.hasTooEarlySlot())
            return false;
        return (startSlot > 0 && startSlot <= iProperties.getTooEarlySlot())
                || (startSlot == 0 && endSlot <= iProperties.getTooEarlySlot());
    }

    public static class HistoryToken {
        private String iType = null;
        private Map<String, String> iParams = new HashMap<String, String>();
        private Map<String, String> iDefaults = new HashMap<String, String>();

        public HistoryToken(PageType type) {
            iType = type.name();

            // 1. take page type defaults --> DEFAULTS
            if (type.getParams() != null)
                for (int i = 0; 1 + i < type.getParams().length; i += 2)
                    iDefaults.put(type.getParams()[i], type.getParams()[i + 1]);

            // 2. take page parameters --> DEFAULTS (on top of the page type defaults)
            for (Map.Entry<String, List<String>> params : Window.Location.getParameterMap().entrySet())
                iDefaults.put(params.getKey(), params.getValue().get(0));

            // 3. take cookie --> PARAMS (override defaults)
            String cookie = EventCookie.getInstance().getHash(iType);
            if (cookie != null) {
                for (String pair : cookie.split("\\&")) {
                    int idx = pair.indexOf('=');
                    if (idx >= 0) {
                        String key = pair.substring(0, idx);
                        if (Location.getParameter(key) == null)
                            iParams.put(key, URL.decodeQueryString(pair.substring(idx + 1)));
                    }
                }
            }

            // 4. take page token (hash) --> PARAMS (override cookie)
            parse(History.getToken());
        }

        public void reset(String token) {
            iParams.clear();
            parse(token);
        }

        public void parse(String token) {
            if (token != null && !token.isEmpty())
                for (String pair : token.split("\\&")) {
                    int idx = pair.indexOf('=');
                    if (idx >= 0)
                        iParams.put(pair.substring(0, idx), URL.decodeQueryString(pair.substring(idx + 1)));
                }
        }

        public void setParameter(String key, String value) {
            if (value == null) {
                iParams.remove(key);
            } else {
                String defaultValue = iDefaults.get(key);
                if (value.equals(defaultValue))
                    iParams.remove(key);
                else
                    iParams.put(key, value);
            }
        }

        public void setParameter(String key, Long value) {
            setParameter(key, value == null ? null : value.toString());
        }

        @Override
        public String toString() {
            return toString(null);
        }

        public String toString(String skip) {
            String ret = "";
            for (String key : new TreeSet<String>(iParams.keySet())) {
                if (key.equals(skip))
                    continue;
                if (!ret.isEmpty())
                    ret += "&";
                ret += key + "=" + URL.encodeQueryString(iParams.get(key));
            }
            return ret;
        }

        public String getParameter(String key, String defaultValue) {
            String value = getParameter(key);
            return (value == null ? defaultValue : value);
        }

        public String getParameter(String key) {
            String value = iParams.get(key);
            return (value == null ? iDefaults.get(key) : value);
        }

        public String getDefaultParameter(String key, String defaultValue) {
            String value = iDefaults.get(key);
            return (value == null ? defaultValue : value);
        }

        public void setDefaultParameter(String key, String value) {
            if (value != null)
                iDefaults.put(key, value);
        }

        public boolean hasParameter(String key) {
            return getParameter(key) != null;
        }

        public boolean isChanged(String key, String value) {
            String v = getParameter(key);
            return (v == null ? value != null : !v.equals(value));
        }

        public boolean isChanged(String key, String defaultValue, String value) {
            String v = getParameter(key);
            return (v == null ? !defaultValue.equals(value) : !v.equals(value));
        }

        public void mark() {
            String token = toString();
            if (!History.getToken().equals(token))
                History.newItem(token, false);
            EventCookie.getInstance().setHash(iType, toString("event"));
        }
    }

    public String getPageName() {
        String pageName = iHistoryToken.getParameter("title", "Events");
        ResourceType resource = getResourceType();
        if (!"true".equals(iHistoryToken.getParameter("fixedTitle")) && resource != null)
            pageName = resource.getPageName();
        return pageName;
    }

    @Override
    public boolean accept(AcademicSession session) {
        String filter = iHistoryToken.getParameter("filter");
        if ("classes".equals(filter))
            return session.has(AcademicSession.Flag.HasClasses);
        if ("exams".equals(filter))
            return session.has(AcademicSession.Flag.HasFinalExams)
                    || session.has(AcademicSession.Flag.HasMidtermExams);
        if ("events".equals(filter))
            return session.has(AcademicSession.Flag.HasEvents);
        if ("person".equals(filter))
            return session.has(AcademicSession.Flag.HasClasses) || session.has(AcademicSession.Flag.HasFinalExams)
                    || session.has(AcademicSession.Flag.HasMidtermExams);
        return true;
    }

    @Override
    public SessionMonth.Flag getDateFlag(EventType type, Date date) {
        if (iSessionMonths == null || iSessionMonths.isEmpty())
            return null;
        if (date == null)
            return null;
        int m = Integer.parseInt(ServerDateTimeFormat.getFormat("MM").format(date));
        for (SessionMonth month : iSessionMonths)
            if (m == month.getMonth() + 1) {
                int d = Integer.parseInt(ServerDateTimeFormat.getFormat("dd").format(date)) - 1;
                if (month.hasFlag(d, SessionMonth.Flag.FINALS) && type != EventType.FinalExam)
                    return SessionMonth.Flag.FINALS;
                if (month.hasFlag(d, SessionMonth.Flag.BREAK))
                    return SessionMonth.Flag.BREAK;
                if (month.hasFlag(d, SessionMonth.Flag.WEEKEND))
                    return SessionMonth.Flag.WEEKEND;
                if (month.hasFlag(d, SessionMonth.Flag.HOLIDAY))
                    return SessionMonth.Flag.HOLIDAY;
                if (month.hasFlag(d, SessionMonth.Flag.MIDTERMS) && type != EventType.MidtermExam)
                    return SessionMonth.Flag.MIDTERMS;
                return null;
            }
        return null;
    }
}