org.tltv.gantt.client.GanttConnector.java Source code

Java tutorial

Introduction

Here is the source code for org.tltv.gantt.client.GanttConnector.java

Source

/*
 * Copyright 2014 Tomi Virtanen
 *
 * Licensed 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.tltv.gantt.client;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.tltv.gantt.Gantt;
import org.tltv.gantt.client.shared.GanttClientRpc;
import org.tltv.gantt.client.shared.GanttServerRpc;
import org.tltv.gantt.client.shared.GanttState;
import org.tltv.gantt.client.shared.Step;

import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.dom.client.Element;
import com.google.gwt.event.dom.client.ScrollEvent;
import com.google.gwt.event.dom.client.ScrollHandler;
import com.google.gwt.event.logical.shared.AttachEvent;
import com.google.gwt.event.logical.shared.AttachEvent.Handler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.Widget;
import com.vaadin.client.BrowserInfo;
import com.vaadin.client.ComponentConnector;
import com.vaadin.client.ConnectorHierarchyChangeEvent;
import com.vaadin.client.LocaleNotLoadedException;
import com.vaadin.client.LocaleService;
import com.vaadin.client.WidgetUtil;
import com.vaadin.client.communication.RpcProxy;
import com.vaadin.client.communication.StateChangeEvent;
import com.vaadin.client.communication.StateChangeEvent.StateChangeHandler;
import com.vaadin.client.ui.AbstractHasComponentsConnector;
import com.vaadin.client.ui.FocusableScrollPanel;
import com.vaadin.client.ui.VScrollTable;
import com.vaadin.client.ui.layout.ElementResizeEvent;
import com.vaadin.client.ui.layout.ElementResizeListener;
import com.vaadin.client.ui.table.TableConnector;
import com.vaadin.shared.Connector;
import com.vaadin.shared.ui.Connect;

/**
 * Connector for client side GWT {@link GanttWidget} and server side
 * {@link Gantt} Vaadin component.
 * 
 * @author Tltv
 * 
 */
@Connect(Gantt.class)
public class GanttConnector extends AbstractHasComponentsConnector {

    GanttServerRpc rpc = RpcProxy.create(GanttServerRpc.class, this);

    String locale;
    long timeZoneOffset = 0;
    GanttDateTimeService dateTimeService;
    boolean notifyHeight = false;

    ComponentConnector delegateScrollConnector;
    FocusableScrollPanel delegateScrollPanelTarget;
    VScrollTable delegateScrollTableTarget;
    HandlerRegistration ganttScrollHandlerRegistration;
    HandlerRegistration scrollDelegateHandlerRegistration;
    // flag indicating that scroll is delegating right now
    boolean ganttDelegatingVerticalScroll = false;
    boolean delegatingVerticalScroll = false;

    Timer ganttScrollDelay = new Timer() {

        @Override
        public void run() {
            ganttDelegatingVerticalScroll = false;
        }
    };

    Timer scrollDelay = new Timer() {

        @Override
        public void run() {
            delegatingVerticalScroll = false;
        }
    };

    /**
     * Scroll handler for Gantt component to delegate to other component.
     */
    final ScrollHandler ganttScrollHandler = new ScrollHandler() {

        @Override
        public void onScroll(ScrollEvent event) {
            if (delegatingVerticalScroll) {
                // if other component is scrolling, don't allow this scroll
                // event
                return;
            }
            ganttScrollDelay.cancel();
            ganttDelegatingVerticalScroll = true;
            int scrollTop = getWidget().getScrollContainer().getScrollTop();
            try {
                delegateScrollPanelTarget.setScrollPosition(scrollTop);
            } finally {
                ganttScrollDelay.schedule(20);
            }
        }
    };
    /**
     * Scroll handler for scroll events from other component that Gantt may
     * react to.
     */
    final ScrollHandler scrollDelegateTargetHandler = new ScrollHandler() {

        @Override
        public void onScroll(ScrollEvent event) {
            if (ganttDelegatingVerticalScroll) {
                // if gantt is scrolling, don't allow this scroll event
                return;
            }
            scrollDelay.cancel();
            int scrollPosition = delegateScrollPanelTarget.getScrollPosition();
            delegatingVerticalScroll = true;
            try {
                getWidget().getScrollContainer().setScrollTop(scrollPosition);
            } finally {
                scrollDelay.schedule(20);
            }
        }
    };

    final StateChangeHandler scrollDelegateTargetStateChangeHandler = new StateChangeHandler() {

        @Override
        public void onStateChanged(StateChangeEvent stateChangeEvent) {
            Scheduler.get().scheduleDeferred(new ScheduledCommand() {

                @Override
                public void execute() {
                    adjustDelegateTargetHeightLazily();
                }
            });
        }
    };

    ElementResizeListener scrollDelegateTargetResizeListener = new ElementResizeListener() {

        @Override
        public void onElementResize(ElementResizeEvent e) {
            Scheduler.get().scheduleDeferred(new ScheduledCommand() {

                @Override
                public void execute() {
                    adjustDelegateTargetHeightLazily();
                }
            });
        }
    };

    Timer lazyAdjustDelegateTargetHeight = new Timer() {

        @Override
        public void run() {
            updateDelegateTargetHeight();
        }
    };

    LocaleDataProvider localeDataProvider = new LocaleDataProvider() {

        @Override
        public String[] getWeekdayNames() {
            try {
                return LocaleService.getDayNames(locale);
            } catch (LocaleNotLoadedException e) {
                GWT.log(e.getMessage(), e);
            }
            // return default
            return new String[] { "sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday" };
        }

        @Override
        public String[] getMonthNames() {
            try {
                return LocaleService.getMonthNames(locale);
            } catch (LocaleNotLoadedException e) {
                GWT.log(e.getMessage(), e);
            }
            // return default
            return new String[] { "January", "February", "March", "April", "May", "June", "July", "August",
                    "September", "October", "November", "December" };
        }

        @Override
        public int getFirstDayOfWeek() {
            try {
                // Gantt uses 1-based index, just as the server-side Java
                // Locale does. Vaadin locale state has 0-based value.
                return LocaleService.getFirstDayOfWeek(locale) + 1;
            } catch (LocaleNotLoadedException e) {
                GWT.log(e.getMessage(), e);
            }
            // return default
            return 1; // sunday
        }

        @Override
        public String formatDate(Date date, String formatStr) {
            if (dateTimeService == null) {
                try {
                    dateTimeService = new GanttDateTimeService(getLocale());
                } catch (LocaleNotLoadedException e) {
                    GWT.log("Could not create DateTimeService for the locale " + getLocale(), e);
                    return "";
                }
            }
            return dateTimeService.formatDate(date, formatStr);
        }

        @Override
        public boolean isTwelveHourClock() {
            try {
                return LocaleService.isTwelveHourClock(locale);
            } catch (LocaleNotLoadedException e) {
                GWT.log(e.getMessage(), e);
            }
            return false;
        }

        @Override
        public String getLocale() {
            return locale;
        }

        @Override
        public long getTimeZoneOffset() {
            return timeZoneOffset;
        }

    };

    GanttRpc ganttRpc = new GanttRpc() {

        @Override
        public void stepClicked(String stepUid) {
            rpc.stepClicked(stepUid);
        }

        @Override
        public void onMove(String stepUid, String newStepUid, long startDate, long endDate) {
            rpc.onMove(stepUid, newStepUid, startDate, endDate);
        }

        @Override
        public void onResize(String stepUid, long startDate, long endDate) {
            rpc.onResize(stepUid, startDate, endDate);
        }

        @Override
        public boolean onStepRelationSelected(StepWidget source, boolean startingPointChanged,
                Element newRelationStepElement) {
            StepWidget sw = findStepWidgetByElement(newRelationStepElement);
            if (sw == null) {
                return false;
            }

            if (startingPointChanged) {
                // source is target (sw is related to source).
                // sw is new predecessor.

                if (sw.getStep().equals(source.getStep().getPredecessor())) {
                    return false;
                } else if (sw.getStep().equals(source.getStep())) {
                    // remove predecessor
                    rpc.onPredecessorChanged(null, source.getStep().getUid(), source.getStep().getUid());
                    return true;
                }
                rpc.onPredecessorChanged(sw.getStep().getUid(), source.getStep().getUid(), null);
            } else {
                // source is original target (sw is new target)

                if (sw.getStep().equals(source.getStep())) {
                    return false;
                } else if (sw.getStep().equals(source.getStep().getPredecessor())) {
                    // remove predecessor
                    rpc.onPredecessorChanged(null, source.getStep().getUid(), source.getStep().getUid());
                    return true;
                }
                rpc.onPredecessorChanged(source.getStep().getPredecessor().getUid(), sw.getStep().getUid(),
                        source.getStep().getUid());
            }
            return true;
        }
    };

    GanttClientRpc ganttClientRpc = new GanttClientRpc() {

        @Override
        public void updateDelegateTargetHeight() {
            GanttConnector.this.adjustDelegateTargetHeightLazily();
        }
    };

    int previousHeight = -1;
    int previousWidth = -1;

    final ElementResizeListener widgetResizeListener = new ElementResizeListener() {

        @Override
        public void onElementResize(ElementResizeEvent e) {
            final int height = e.getElement().getClientHeight();
            final int width = e.getElement().getClientWidth();
            if (previousHeight != height) {
                previousHeight = height;

                Scheduler.get().scheduleDeferred(new ScheduledCommand() {

                    @Override
                    public void execute() {
                        getWidget().notifyHeightChanged(height);
                        updateDelegateTargetHeight();
                    }
                });
            }
            if (previousWidth != width) {
                previousWidth = width;
                Scheduler.get().scheduleDeferred(new ScheduledCommand() {

                    @Override
                    public void execute() {
                        getWidget().notifyWidthChanged(width);
                        updateAllStepsPredecessors();
                        updateDelegateTargetHeight();
                    }
                });
            }
        }
    };

    public GanttConnector() {
        registerRpc(GanttClientRpc.class, ganttClientRpc);
    }

    @Override
    protected void init() {
        super.init();
        BrowserInfo info = BrowserInfo.get();
        getWidget().setBrowserInfo(info.isIE(), info.isIE8(), info.isIE9(), info.isChrome(), info.isSafari(),
                info.isWebkit());
        // If background grid is not needed, ie9 works without
        // setting alwaysCalculatePixelWidths flag to true.
        getWidget()
                .setAlwaysCalculatePixelWidths(info.isSafari() || info.isOpera() || info.isIE8() || info.isIE9());
        getWidget().setTouchSupported(info.isTouchDevice());
        getWidget().initWidget(ganttRpc, localeDataProvider);
        getLayoutManager().addElementResizeListener(getWidget().getElement(), widgetResizeListener);
    }

    @Override
    public void onUnregister() {
        getLayoutManager().removeElementResizeListener(getWidget().getElement(), widgetResizeListener);
        unRegisterScrollDelegateHandlers();
    }

    @Override
    protected Widget createWidget() {
        return GWT.create(GanttWidget.class);
    }

    @Override
    public GanttWidget getWidget() {
        return (GanttWidget) super.getWidget();
    }

    @Override
    public GanttState getState() {
        return (GanttState) super.getState();
    }

    @Override
    public void onStateChanged(StateChangeEvent stateChangeEvent) {
        super.onStateChanged(stateChangeEvent);

        locale = getState().locale;
        timeZoneOffset = (getState().timeZoneOffset != null ? getState().timeZoneOffset : 0);
        if (stateChangeEvent.hasPropertyChanged("locale")) {
            dateTimeService = null;
        }

        final boolean changeHasInpactToSteps = stateChangeEvent.hasPropertyChanged("resolution")
                || stateChangeEvent.hasPropertyChanged("startDate")
                || stateChangeEvent.hasPropertyChanged("endDate");

        if (stateChangeEvent.hasPropertyChanged("monthRowVisible")
                || stateChangeEvent.hasPropertyChanged("yearRowVisible")
                || stateChangeEvent.hasPropertyChanged("monthFormat")
                || stateChangeEvent.hasPropertyChanged("yearFormat")
                || stateChangeEvent.hasPropertyChanged("weekFormat")
                || stateChangeEvent.hasPropertyChanged("dayFormat")) {
            notifyHeight = !stateChangeEvent.isInitialStateChange();
            getWidget().setForceUpdateTimeline();
        }
        if (!notifyHeight && stateChangeEvent.hasPropertyChanged("resolution")) {
            notifyHeight = !stateChangeEvent.isInitialStateChange();
        }

        if (stateChangeEvent.hasPropertyChanged("readOnly")) {
            getWidget().setMovableSteps(!getState().readOnly && getState().movableSteps);
            getWidget().setResizableSteps(!getState().readOnly && getState().resizableSteps);
            for (StepWidget s : getSteps()) {
                s.setReadOnly(getState().readOnly);
            }
        }

        if (stateChangeEvent.hasPropertyChanged("verticalScrollDelegateTarget")) {
            handleVerticalScrollDelegateTargetChange();
        }

        Scheduler.get().scheduleDeferred(new ScheduledCommand() {

            @Override
            public void execute() {
                getWidget().update(getSteps());
                if (notifyHeight) {
                    getWidget().notifyHeightChanged(previousHeight);
                }
                if (changeHasInpactToSteps) {
                    updateAllStepsPredecessors();
                }
                updateVerticalScrollDelegation();
                adjustDelegateTargetHeightLazily();
            }
        });
    }

    protected List<StepWidget> getSteps() {
        List<StepWidget> steps = new ArrayList<StepWidget>();
        for (Connector sc : getState().steps) {
            steps.add(((StepConnector) sc).getWidget());
        }
        return steps;
    }

    protected StepWidget findStepWidgetByElement(Element target) {
        for (Widget w : getSteps()) {
            if (w.getElement().isOrHasChild(target)) {
                if (w instanceof StepWidget) {
                    return (StepWidget) w;
                }
            }
        }
        return null;
    }

    protected Map<Step, StepWidget> getStepsMap() {
        Map<Step, StepWidget> steps = new HashMap<Step, StepWidget>();
        StepWidget stepWidget;
        for (Connector sc : getState().steps) {
            stepWidget = ((StepConnector) sc).getWidget();
            steps.put(((StepConnector) sc).getState().step, stepWidget);
        }
        return steps;
    }

    void handleVerticalScrollDelegateTargetChange() {
        Connector c = getState().verticalScrollDelegateTarget;
        unRegisterScrollDelegateHandlers();

        delegateScrollConnector = null;
        delegateScrollTableTarget = null;
        delegateScrollPanelTarget = null;
        if (c instanceof TableConnector) {
            delegateScrollConnector = (TableConnector) c;
            VScrollTable scrolltable = ((TableConnector) c).getWidget();
            delegateScrollTableTarget = scrolltable;
            delegateScrollPanelTarget = scrolltable.scrollBodyPanel;
            registerScrollDelegateHandlers();
        }
    }

    void unRegisterScrollDelegateHandlers() {
        if (scrollDelegateHandlerRegistration != null) {
            scrollDelegateHandlerRegistration.removeHandler();
        }
        if (ganttScrollHandlerRegistration != null) {
            ganttScrollHandlerRegistration.removeHandler();
        }
        if (delegateScrollConnector != null) {
            delegateScrollConnector.removeStateChangeHandler(scrollDelegateTargetStateChangeHandler);
        }
        if (delegateScrollTableTarget != null) {
            getLayoutManager().removeElementResizeListener(delegateScrollTableTarget.getElement(),
                    scrollDelegateTargetResizeListener);
        }
    }

    void registerScrollDelegateHandlers() {
        delegateScrollConnector.addStateChangeHandler(scrollDelegateTargetStateChangeHandler);
        getLayoutManager().addElementResizeListener(delegateScrollTableTarget.getElement(),
                scrollDelegateTargetResizeListener);
    }

    void updateVerticalScrollDelegation() {
        if (delegateScrollPanelTarget == null) {
            return; // scroll delegation is not set
        }
        // register scroll handler to Gantt widget
        ganttScrollHandlerRegistration = getWidget().addDomHandler(ganttScrollHandler, ScrollEvent.getType());

        // register a scroll handler to 'delegation' scroll panel.
        scrollDelegateHandlerRegistration = delegateScrollPanelTarget.addScrollHandler(scrollDelegateTargetHandler);

        // add detach listener to unregister scroll handler when its detached.
        delegateScrollPanelTarget.addAttachHandler(new Handler() {

            @Override
            public void onAttachOrDetach(AttachEvent event) {
                if (!event.isAttached() && scrollDelegateHandlerRegistration != null) {
                    scrollDelegateHandlerRegistration.removeHandler();
                }
            }
        });
    }

    void updateDelegateTargetHeight() {
        if (delegateScrollTableTarget == null) {
            return;
        }

        int headerHeight = 0;
        if (delegateScrollTableTarget.tHead != null) {
            // update table header height to match the Gantt widget's header
            // height
            int border = WidgetUtil.measureVerticalBorder(delegateScrollTableTarget.tHead.getElement());
            headerHeight = getWidget().getTimelineHeight();

            delegateScrollTableTarget.tHead.setHeight(Math.max(0, headerHeight - border) + "px");
        }

        int border = WidgetUtil.measureVerticalBorder(delegateScrollPanelTarget.getElement());
        // Adjust table's scroll container height to match the Gantt widget's
        // scroll container height.
        int newTableScrollContainerHeight = getWidget().getScrollContainerHeight();
        boolean tableHorScrollbarVisible = border >= WidgetUtil.getNativeScrollbarSize();
        if (getWidget().isContentOverflowingHorizontally()) {
            getWidget().hideHorizontalScrollbarSpacer();
            if (tableHorScrollbarVisible) {
                newTableScrollContainerHeight += WidgetUtil.getNativeScrollbarSize();
            }
        } else {
            if (tableHorScrollbarVisible) {
                getWidget().showHorizontalScrollbarSpacer();
            } else {
                getWidget().hideHorizontalScrollbarSpacer();
            }
        }

        delegateScrollPanelTarget.setHeight(Math.max(0, newTableScrollContainerHeight) + "px");

        getLayoutManager().setNeedsMeasure((ComponentConnector) getState().verticalScrollDelegateTarget);
    }

    void adjustDelegateTargetHeightLazily() {
        lazyAdjustDelegateTargetHeight.cancel();
        // delay must be more than VScrollTable widget's lazy column adjusting.
        lazyAdjustDelegateTargetHeight.schedule(350);
    }

    @Override
    public void updateCaption(ComponentConnector connector) {
    }

    @Override
    public void onConnectorHierarchyChange(ConnectorHierarchyChangeEvent connectorHierarchyChangeEvent) {

        // StepConnector handles adding new step.
        // Here we handle removing and other necessary changed related
        // hierarchy.
        Set<StepWidget> predecessorRemoved = new HashSet<StepWidget>();
        // remove old steps
        for (ComponentConnector c : connectorHierarchyChangeEvent.getOldChildren()) {
            if (!getChildComponents().contains(c)) {
                StepWidget stepWidget = ((StepConnector) c).getWidget();
                getWidget().removeStep(stepWidget);
                predecessorRemoved.add(stepWidget);
            }
        }

        Map<Step, StepWidget> steps = getStepsMap();

        // update new steps with references to gantt widget and locale data
        // provider.
        for (ComponentConnector c : getChildComponents()) {
            StepWidget stepWidget = ((StepConnector) c).getWidget();
            if (!connectorHierarchyChangeEvent.getOldChildren().contains(c)) {
                stepWidget.setGantt(getWidget(), localeDataProvider);
            }

            Step predecessor = ((StepConnector) c).getState().step.getPredecessor();
            if (predecessor != null && !predecessorRemoved.contains(stepWidget)) {
                stepWidget.setPredecessorStepWidget(steps.get(predecessor));
            } else {
                stepWidget.setPredecessorStepWidget(null);
            }
        }

        deferredUpdateAllStepsPredecessors();
    }

    /** Updates all steps predecessor visualizations. */
    public void updateAllStepsPredecessors() {
        for (ComponentConnector c : getChildComponents()) {
            StepWidget stepWidget = ((StepConnector) c).getWidget();
            stepWidget.updatePredecessor();
        }
    }

    private void deferredUpdateAllStepsPredecessors() {
        Scheduler.get().scheduleDeferred(new ScheduledCommand() {
            @Override
            public void execute() {
                updateAllStepsPredecessors();
            }
        });
    }

    /**
     * Return {@link StepWidget} objects that are related to the given
     * StepWidget. Via {@link Step#getPredecessor()} for example.
     */
    public Set<StepWidget> findRelatedSteps(Step targetStep, List<ComponentConnector> stepConnectors) {
        Set<StepWidget> widgets = new HashSet<StepWidget>();
        for (ComponentConnector con : stepConnectors) {
            StepWidget stepWidget = ((StepConnector) con).getWidget();
            if (targetStep.equals(stepWidget.getStep().getPredecessor())) {
                widgets.add(stepWidget);
            }
        }
        return widgets;
    }

    public StepWidget getStepWidget(Step target) {
        return getStepsMap().get(target);
    }
}