com.vaadin.addon.spreadsheet.client.SheetTabSheet.java Source code

Java tutorial

Introduction

Here is the source code for com.vaadin.addon.spreadsheet.client.SheetTabSheet.java

Source

package com.vaadin.addon.spreadsheet.client;

/*
 * #%L
 * Vaadin Spreadsheet
 * %%
 * Copyright (C) 2013 - 2015 Vaadin Ltd
 * %%
 * This program is available under Commercial Vaadin Add-On License 3.0
 * (CVALv3).
 * 
 * See the file license.html distributed with this software for more
 * information about licensing.
 * 
 * You should have received a copy of the CVALv3 along with this program.
 * If not, see <http://vaadin.com/license/cval-3>.
 * #L%
 */

import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.JsArray;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.dom.client.DivElement;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.InputElement;
import com.google.gwt.dom.client.Style.Display;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.EventListener;
import com.google.gwt.user.client.ui.Widget;
import com.vaadin.client.ComputedStyle;
import com.vaadin.client.WidgetUtil;

public class SheetTabSheet extends Widget {

    private static final String HIDDEN = "hidden";

    public interface SheetTabSheetHandler {
        public void onSheetTabSelected(int tabIndex);

        public void onSheetRename(int selectedTabIndex, String value);

        public void onNewSheetCreated();

        public void onSheetRenameCancel();

        public void onFirstTabIndexChange(int tabScrollIndex);

        public void onSheetTabSheetFocus();
    }

    private static final String SELECTED_TAB_CLASSNAME = "selected-tab";

    private DivElement root = Document.get().createDivElement();
    // div containing the tabs
    private DivElement container = Document.get().createDivElement();

    private DivElement options = Document.get().createDivElement();

    private DivElement scrollBeginning = Document.get().createDivElement();

    private DivElement scrollEnd = Document.get().createDivElement();

    private DivElement scrollLeft = Document.get().createDivElement();

    private DivElement scrollRight = Document.get().createDivElement();

    private DivElement addNewSheet = Document.get().createDivElement();

    private InputElement input = Document.get().createTextInputElement();

    private DivElement tempElement = Document.get().createDivElement();

    private JsArray<JavaScriptObject> tabs = JsArray.createArray().cast();

    private final SheetTabSheetHandler handler;

    private int selectedTabIndex = -1;

    private int tabScrollIndex;

    private double tabScrollMargin;

    private boolean readOnly;

    private boolean editing;

    private String cachedSheetName = "";

    private DivElement infoLabel = Document.get().createDivElement();

    public SheetTabSheet(SheetTabSheetHandler handler) {
        this.handler = handler;

        initDOM();
        initListeners();

        input.setMaxLength(31);
    }

    private void initDOM() {
        scrollBeginning.setClassName("scroll-tabs-beginning");
        scrollEnd.setClassName("scroll-tabs-end");
        scrollLeft.setClassName("scroll-tabs-left");
        scrollRight.setClassName("scroll-tabs-right");
        addNewSheet.setClassName("add-new-tab");

        options.setClassName("sheet-tabsheet-options");
        options.appendChild(scrollBeginning);
        options.appendChild(scrollLeft);
        options.appendChild(scrollRight);
        options.appendChild(scrollEnd);
        options.appendChild(addNewSheet);

        container.setClassName("sheet-tabsheet-container");

        tempElement.setClassName("sheet-tabsheet-temp");
        root.appendChild(tempElement);

        root.setClassName("sheet-tabsheet");
        root.appendChild(options);
        root.appendChild(container);

        infoLabel.setClassName("sheet-tabsheet-infolabel");
        root.appendChild(infoLabel);

        setElement(root);
    }

    private void initListeners() {
        Event.sinkEvents(root, Event.ONCLICK | Event.ONDBLCLICK);
        Event.setEventListener(root, new EventListener() {

            @Override
            public void onBrowserEvent(Event event) {
                final Element target = event.getEventTarget().cast();
                final int type = event.getTypeInt();
                if (target.equals(input)) {
                    return;
                }
                event.stopPropagation();
                if (type == Event.ONCLICK) {
                    if (editing && !readOnly) {
                        commitSheetName();
                    }
                    handler.onSheetTabSheetFocus();
                    if (options.isOrHasChild(target) && !target.hasClassName(HIDDEN)) {
                        if (target.equals(scrollBeginning)) {
                            tabScrollMargin = 0;
                            tabScrollIndex = 0;
                            container.getStyle().setMarginLeft(tabScrollMargin, Unit.PX);
                            showHideScrollIcons();
                            handler.onFirstTabIndexChange(tabScrollIndex);
                        } else if (target.equals(scrollLeft)) {
                            if (tabScrollIndex > 0) {
                                tabScrollIndex--;
                                if (tabScrollIndex == 0) {
                                    tabScrollMargin = 0;
                                } else {
                                    tabScrollMargin += getTabWidth(tabScrollIndex);
                                }
                                container.getStyle().setMarginLeft(tabScrollMargin, Unit.PX);
                            }
                            showHideScrollIcons();
                            handler.onFirstTabIndexChange(tabScrollIndex);
                        } else if (target.equals(scrollRight)) {
                            if (tabScrollIndex < (tabs.length() - 1)) {
                                tabScrollMargin -= getTabWidth(tabScrollIndex);
                                container.getStyle().setMarginLeft(tabScrollMargin, Unit.PX);
                                tabScrollIndex++;
                                showHideScrollIcons();
                                handler.onFirstTabIndexChange(tabScrollIndex);
                            }
                        } else if (target.equals(scrollEnd)) {
                            int tempIndex = getLastTabVisibleWithScrollIndex();
                            setFirstVisibleTab(tempIndex);
                            handler.onFirstTabIndexChange(tabScrollIndex);
                        } else if (target.equals(addNewSheet)) {
                            if (!readOnly) {
                                handler.onNewSheetCreated();
                            }
                        }
                    } else if (container.isOrHasChild(target)) {
                        for (int i = 0; i < tabs.length(); i++) {
                            if (tabs.get(i).equals(target)) {
                                if (i != selectedTabIndex) {
                                    handler.onSheetTabSelected(i);
                                }
                            }
                        }
                    }
                } else if (type == Event.ONDBLCLICK) {
                    if (!readOnly) {
                        for (int i = 0; i < tabs.length(); i++) {
                            if (tabs.get(i).equals(target)) {
                                if (i != selectedTabIndex) {
                                    handler.onSheetTabSelected(i);
                                } else {
                                    editing = true;
                                    Element e = tabs.get(i).cast();
                                    cachedSheetName = e.getInnerText();
                                    input.setValue(cachedSheetName);
                                    e.setInnerText("");
                                    e.appendChild(input);
                                    input.focus();
                                    updateInputSize();
                                }
                            }
                        }
                    }
                }
            }

        });
        Event.sinkEvents(input, Event.ONKEYDOWN | Event.ONKEYUP | Event.ONBLUR);
        Event.setEventListener(input, new EventListener() {

            @Override
            public void onBrowserEvent(Event event) {
                final int type = event.getTypeInt();
                if (editing) {
                    if (type == Event.ONBLUR) {
                        commitSheetName();
                    } else {
                        switch (event.getKeyCode()) {
                        case KeyCodes.KEY_ENTER:
                        case KeyCodes.KEY_TAB:
                            commitSheetName();
                            break;
                        case KeyCodes.KEY_ESCAPE:
                            editing = false;
                            input.removeFromParent();
                            Element element = (Element) tabs.get(selectedTabIndex).cast();
                            element.getStyle().clearWidth();
                            element.setInnerText(cachedSheetName);
                            handler.onSheetRenameCancel();
                            break;
                        default:
                            doDeferredInputSizeUpdate();
                            break;
                        }
                    }
                }
                event.stopPropagation();
            }
        });
    }

    /**
     * Sets the content of the info label.
     *
     * @param value
     *            the new content. Can not be HTML.
     */
    public void setInfoLabelValue(String value) {
        if (value == null) {
            infoLabel.getStyle().setDisplay(Display.NONE);
            container.getStyle().setMarginRight(0, Unit.PX);
        } else {
            container.getStyle().setMarginRight(206, Unit.PX);
            infoLabel.getStyle().setDisplay(Display.INLINE);
            infoLabel.setInnerText(value);
        }
    }

    /**
     * @return current content of the info label.
     */
    public String getInfoLabelValue() {
        return infoLabel.getInnerText();
    }

    private double getTabWidth(int index) {
        Element tab = ((Element) tabs.get(index).cast());
        double result = WidgetUtil.getRequiredWidthBoundingClientRectDouble(tab);
        ComputedStyle cs = new ComputedStyle(tab);
        result += cs.getMargin()[1];
        result += cs.getMargin()[3];
        return result;
    }

    private void doDeferredInputSizeUpdate() {
        Scheduler.get().scheduleDeferred(new ScheduledCommand() {

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

    private int getLastTabVisibleWithScrollIndex() {
        return getTabVisibleWithScrollIndex(tabs.length() - 1);
    }

    private int getTabVisibleWithScrollIndex(int tabIndex) {
        int tempWidth = root.getOffsetWidth() - ((Element) options.cast()).getOffsetWidth();
        if (!infoLabel.getStyle().getDisplay().equals("none")) {
            tempWidth -= ((Element) infoLabel.cast()).getOffsetWidth();
        }
        tempWidth -= getTabWidth(tabIndex);

        while (tabIndex > 0 && tempWidth - getTabWidth(tabIndex - 1) > 0) {
            tabIndex--;
            tempWidth -= getTabWidth(tabIndex);
        }
        return tabIndex;
    }

    private void updateInputSize() {
        String text = input.getValue();
        if (text.length() > 31) {
            text = text.substring(0, 31);
            input.setValue(text);
        }
        tempElement.setInnerText(text);
        int textWidth = tempElement.getOffsetWidth();
        if (textWidth < 50) {
            textWidth = 50;
        }
        // check that the edited tab doesn't overflow the tab sheet
        Element selectedTab = (Element) tabs.get(selectedTabIndex).cast();
        int rootAbsoluteRight = root.getAbsoluteRight();
        int selectedTabAbsoluteRight = selectedTab.getAbsoluteRight() + 10;
        while (selectedTabAbsoluteRight > rootAbsoluteRight && tabScrollIndex < (tabs.length() - 1)) {
            double width = getTabWidth(tabScrollIndex);
            selectedTabAbsoluteRight -= width;
            tabScrollMargin -= width;
            tabScrollIndex++;
        }
        container.getStyle().setMarginLeft(tabScrollMargin, Unit.PX);
        input.getStyle().setWidth(textWidth + 5, Unit.PX);
        selectedTab.getStyle().setWidth(textWidth, Unit.PX);
    }

    private void commitSheetName() {
        editing = false;
        input.removeFromParent();
        Element selectedTab = tabs.get(selectedTabIndex).cast();
        selectedTab.getStyle().clearWidth();
        String value = input.getValue();
        if (validateSheetName(value) && !cachedSheetName.equals(value)) {
            for (int i = 0; i < tabs.length(); i++) {
                // value cannot be the same as with another sheet
                if (value.equals(((Element) tabs.get(i).cast()).getInnerText())) {
                    selectedTab.setInnerText(cachedSheetName);
                    return;
                }
            }
            handler.onSheetRename(selectedTabIndex, value);
            selectedTab.setInnerText(value);
            showHideScrollIcons();
        } else {
            // TODO show error ?
            selectedTab.setInnerText(cachedSheetName);
        }
    }

    private boolean validateSheetName(String sheetName) {
        if (sheetName == null) {
            return false;
        }
        int len = sheetName.length();
        if (len < 1 || len > 31) {
            return false;
        }

        for (int i = 0; i < len; i++) {
            char ch = sheetName.charAt(i);
            switch (ch) {
            case '/':
            case '\\':
            case '?':
            case '*':
            case ']':
            case '[':
            case ':':
                return false;
            default:
                // all other chars OK
                continue;
            }
        }
        if (sheetName.charAt(0) == '\'' || sheetName.charAt(len - 1) == '\'') {
            return false;
        }
        return true;
    }

    private Element createTabElement(String tabName) {
        final Element e = Document.get().createDivElement();
        e.setInnerText(tabName);
        e.setClassName("sheet-tabsheet-tab");
        return e;
    }

    public void addTabs(String[] tabNames) {
        for (String tabName : tabNames) {
            Element e = createTabElement(tabName);
            container.appendChild(e);
            tabs.push(e);
        }
        showHideScrollIcons();
    }

    public void setTabs(String[] tabNames, boolean clearScrollPosition) {
        // remove unnecessary tabs
        if (clearScrollPosition) {
            container.getStyle().clearMarginLeft();
            tabScrollIndex = 0;
            tabScrollMargin = 0;
        }
        for (int i = tabNames.length; i < tabs.length(); i++) {
            ((Element) tabs.get(i).cast()).removeFromParent();
        }
        tabs.setLength(tabNames.length);
        for (int i = 0; i < tabNames.length; i++) {
            JavaScriptObject jso = tabs.get(i);
            if (jso != null) {
                ((Element) jso.cast()).setInnerText(tabNames[i]);
            } else {
                Element newTab = createTabElement(tabNames[i]);
                container.appendChild(newTab);
                tabs.set(i, newTab);
            }
        }
        if (selectedTabIndex >= (tabs.length())) {
            selectedTabIndex = -1;
        }

        showHideScrollIcons();
    }

    public void removeAllTabs() {
        for (int i = 0; i < tabs.length(); i++) {
            Element e = tabs.get(i).cast();
            e.removeFromParent();
        }
        container.getStyle().clearMarginLeft();
        tabs.setLength(0);
        selectedTabIndex = -1;
        tabScrollIndex = 0;
    }

    /**
     *
     * @param sheetIndex
     *            1-based
     */
    public void setSelectedTab(int sheetIndex) {
        if (selectedTabIndex != -1) {
            ((Element) tabs.get(selectedTabIndex).cast()).removeClassName(SELECTED_TAB_CLASSNAME);
        }
        selectedTabIndex = sheetIndex - 1;
        Element selectedTab = ((Element) tabs.get(selectedTabIndex).cast());
        selectedTab.addClassName(SELECTED_TAB_CLASSNAME);
        if (tabScrollIndex > selectedTabIndex) {
            setFirstVisibleTab(selectedTabIndex);
        } else if (root.getAbsoluteRight() < selectedTab.getAbsoluteRight() && !editing) {
            int tempIndex = getTabVisibleWithScrollIndex(selectedTabIndex);
            setFirstVisibleTab(tempIndex);
        }
    }

    public void setReadOnly(boolean readOnly) {
        this.readOnly = readOnly;
        addNewSheet.getStyle().setDisplay(readOnly ? Display.NONE : Display.INLINE_BLOCK);
    }

    public void setFirstVisibleTab(int firstVisibleTab) {
        if (tabScrollIndex < firstVisibleTab) {
            do {
                tabScrollMargin -= getTabWidth(tabScrollIndex);
                tabScrollIndex++;
            } while (tabScrollIndex < firstVisibleTab);
            container.getStyle().setMarginLeft(tabScrollMargin, Unit.PX);
        } else if (tabScrollIndex > firstVisibleTab) {
            do {
                tabScrollIndex--;
                tabScrollMargin += getTabWidth(tabScrollIndex);
            } while (tabScrollIndex > firstVisibleTab);
            container.getStyle().setMarginLeft(tabScrollMargin, Unit.PX);
        }
        showHideScrollIcons();
    }

    private void showHideScrollIcons() {
        if (tabScrollIndex == 0) {
            scrollLeft.addClassName(HIDDEN);
            scrollBeginning.addClassName(HIDDEN);
        } else {
            scrollLeft.removeClassName(HIDDEN);
            scrollBeginning.removeClassName(HIDDEN);
        }
        int lastTabVisibleWithScrollIndex = getLastTabVisibleWithScrollIndex();
        if (tabScrollIndex < lastTabVisibleWithScrollIndex) {
            scrollRight.removeClassName(HIDDEN);
            scrollEnd.removeClassName(HIDDEN);
        } else {
            scrollRight.addClassName(HIDDEN);
            scrollEnd.addClassName(HIDDEN);
        }
    }

    public void onWidgetResize() {
        // check if we need to display scroll buttons
        showHideScrollIcons();
    }
}