org.openxdata.designer.client.view.FormsTreeView.java Source code

Java tutorial

Introduction

Here is the source code for org.openxdata.designer.client.view.FormsTreeView.java

Source

package org.openxdata.designer.client.view;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Vector;

import org.openxdata.designer.client.Context;
import org.openxdata.designer.client.controller.IFormActionListener;
import org.openxdata.designer.client.controller.IFormChangeListener;
import org.openxdata.designer.client.controller.IFormDesignerListener;
import org.openxdata.designer.client.controller.IFormSelectionListener;
import org.openxdata.designer.client.event.FormDesignerEventBus;
import org.openxdata.designer.client.event.XformItemSelectEvent;
import org.openxdata.designer.client.event.XformItemSelectEvent.XformItemType;
import org.openxdata.designer.client.event.XformListEmptyEvent;
import org.openxdata.designer.client.util.FormDesignerUtil;
import org.openxdata.designer.client.vew.widget.images.FormDesignerImages;
import org.openxdata.designer.client.widget.CompositeTreeItem;
import org.openxdata.designer.client.widget.TreeItemWidget;
import org.openxdata.sharedlib.client.locale.FormsConstants;
import org.openxdata.sharedlib.client.model.FormDef;
import org.openxdata.sharedlib.client.model.ModelConstants;
import org.openxdata.sharedlib.client.model.OptionDef;
import org.openxdata.sharedlib.client.model.PageDef;
import org.openxdata.sharedlib.client.model.QuestionDef;
import org.openxdata.sharedlib.client.util.FormUtil;

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.event.dom.client.MouseDownEvent;
import com.google.gwt.event.dom.client.MouseDownHandler;
import com.google.gwt.event.dom.client.MouseUpEvent;
import com.google.gwt.event.dom.client.MouseUpHandler;
import com.google.gwt.event.logical.shared.SelectionEvent;
import com.google.gwt.event.logical.shared.SelectionHandler;
import com.google.gwt.event.shared.EventBus;
import com.google.gwt.resources.client.ImageResource;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.MenuBar;
import com.google.gwt.user.client.ui.PopupPanel;
import com.google.gwt.user.client.ui.Tree;
import com.google.gwt.user.client.ui.TreeItem;
import org.openxdata.sharedlib.client.model.QuestionType;

/**
 * Displays questions in a tree view.
 * 
 *  www.openxdata.org - Licensed as written in license.txt and original sources of this file and its authors are found in sources.txt.
 *
 */
public class FormsTreeView extends Composite
        implements SelectionHandler<TreeItem>, IFormChangeListener, IFormActionListener {

    final FormsConstants i18n = GWT.create(FormsConstants.class);

    /** The main or root widget for displaying the list of forms and their contents
     * in a tree view.
     */
    private Tree tree;

    /** The tree images. */
    private final FormDesignerImages images;

    /** Pop up for displaying tree item context menu. */
    private PopupPanel popup;

    /** The item that has been copied to the clipboard. */
    private Object clipboardItem;
    private boolean inCutMode = false;

    /** The currently selected tree item. */
    private TreeItem item;

    /** Flag determining whether to set the form node as the root tree node. */
    private boolean showFormAsRoot;

    /** The currently selected form. */
    private FormDef formDef;

    /** List of form item selection listeners. */
    private List<IFormSelectionListener> formSelectionListeners = new ArrayList<IFormSelectionListener>();

    /** The next available form id. */
    private int nextFormId = 0;

    /** The next available page id. */
    private int nextPageId = 0;

    /** The next available question id. */
    private int nextQuestionId = 0;

    /** The next available question option id. */
    private int nextOptionId = 0;

    /** The listener to form designer global events. */
    private IFormDesignerListener formDesignerListener;

    private final EventBus eventBus = FormDesignerEventBus.getBus();

    /**
     * Creates a new instance of the forms tree view widget.
     * 
     * @param images the tree images.
     * @param formSelectionListener the form item selection events listener.
     */
    public FormsTreeView(FormDesignerImages images, IFormSelectionListener formSelectionListener) {

        this.images = images;
        this.formSelectionListeners.add(formSelectionListener);

        tree = new Tree(images);

        initWidget(tree);
        FormUtil.maximizeWidget(tree);

        tree.addSelectionHandler(this);
        tree.ensureSelectedItemVisible();

        //This is just for solving a wiered behaviour when one changes a node text
        //and the click another node which gets the same text as the previously
        //selected text. Just comment it out and you will see what happens.
        tree.addMouseDownHandler(new MouseDownHandler() {
            public void onMouseDown(MouseDownEvent event) {
                tree.setSelectedItem(tree.getSelectedItem());
                scrollToLeft();
            }
        });

        tree.addMouseUpHandler(new MouseUpHandler() {
            public void onMouseUp(MouseUpEvent event) {
                scrollToLeft();
            }
        });

        initContextMenu();
    }

    private void scrollToLeft() {
        Scheduler.get().scheduleDeferred(new ScheduledCommand() {
            public void execute() {
                Element element = (Element) getParent().getParent().getParent().getElement().getChildNodes()
                        .getItem(0).getChildNodes().getItem(0);
                DOM.setElementPropertyInt(element, "scrollLeft", 0);
            }
        });
    }

    /**
     * Sets the listener for form designer global events.
     * 
     * @param formDesignerListener the listener.
     */
    public void setFormDesignerListener(IFormDesignerListener formDesignerListener) {
        this.formDesignerListener = formDesignerListener;
    }

    /**
     * Adds a listener to form item selection events.
     * 
     * @param formSelectionListener the listener to add.
     */
    public void addFormSelectionListener(IFormSelectionListener formSelectionListener) {
        this.formSelectionListeners.add(formSelectionListener);
    }

    public void showFormAsRoot(boolean showFormAsRoot) {
        this.showFormAsRoot = showFormAsRoot;
    }

    /**
     * Prepares the tree item context menu.
     */
    private void initContextMenu() {
        popup = new PopupPanel(true, true);

        MenuBar menuBar = new MenuBar(true);
        menuBar.addItem(FormDesignerUtil.createHeaderHTML(images.add(), i18n.addNew()), true, new Command() {
            public void execute() {
                popup.hide();
                addNewItem();
            }
        });

        menuBar.addSeparator();
        menuBar.addItem(FormDesignerUtil.createHeaderHTML(images.addchild(), i18n.addNewChild()), true,
                new Command() {
                    public void execute() {
                        popup.hide();
                        addNewChildItem();
                    }
                });

        menuBar.addSeparator();
        menuBar.addItem(FormDesignerUtil.createHeaderHTML(images.delete(), i18n.deleteItem()), true, new Command() {
            public void execute() {
                popup.hide();
                deleteSelectedItem();
            }
        });

        menuBar.addSeparator();
        menuBar.addItem(FormDesignerUtil.createHeaderHTML(images.moveup(), i18n.moveUp()), true, new Command() {
            public void execute() {
                popup.hide();
                moveItemUp();
            }
        });

        menuBar.addItem(FormDesignerUtil.createHeaderHTML(images.movedown(), i18n.moveDown()), true, new Command() {
            public void execute() {
                popup.hide();
                moveItemDown();
            }
        });

        menuBar.addSeparator();
        menuBar.addItem(FormDesignerUtil.createHeaderHTML(images.cut(), i18n.cut()), true, new Command() {
            public void execute() {
                popup.hide();
                cutItem();
            }
        });

        menuBar.addItem(FormDesignerUtil.createHeaderHTML(images.copy(), i18n.copy()), true, new Command() {
            public void execute() {
                popup.hide();
                copyItem();
            }
        });

        menuBar.addItem(FormDesignerUtil.createHeaderHTML(images.paste(), i18n.paste()), true, new Command() {
            public void execute() {
                popup.hide();
                pasteItem();
            }
        });

        menuBar.addSeparator();
        menuBar.addItem(FormDesignerUtil.createHeaderHTML(images.save(), i18n.save()), true, new Command() {
            public void execute() {
                popup.hide();
                saveItem();
            }
        });

        menuBar.addSeparator();
        menuBar.addItem(FormDesignerUtil.createHeaderHTML(images.refresh(), i18n.refresh()), true, new Command() {
            public void execute() {
                popup.hide();
                refreshItem();
            }
        });

        popup.setWidget(menuBar);
    }

    /**
     * A helper method to simplify adding tree items that have attached images.
     * {@link #addImageItem(TreeItem, String) code}
     * 
     * @param root the tree item to which the new item will be added.
     * @param title the text associated with this item.
     */
    private TreeItem addImageItem(TreeItem root, String title, ImageResource imageProto, Object userObj,
            String helpText) {
        TreeItem item = new CompositeTreeItem(new TreeItemWidget(imageProto, title, popup, this));
        item.setUserObject(userObj);
        item.setTitle(helpText);
        if (root != null)
            root.addItem(item);
        else
            tree.addItem(item);
        return item;
    }

    /**
     * @see com.google.gwt.event.logical.shared.SelectionHandler#onSelection(SelectionEvent)
     */
    public void onSelection(SelectionEvent<TreeItem> event) {

        scrollToLeft();

        TreeItem item = event.getSelectedItem();

        //Should not call this more than once for the same selected item.
        if (item != this.item) {
            Context.setFormDef(FormDef.getFormDef(item.getUserObject()));
            formDef = Context.getFormDef();

            fireFormItemSelected(item.getUserObject());
            this.item = item;

            //Expand if has kids such that users do not have to click the plus
            //sign to expand. Besides, some are not even aware of that.
            //if(item.getChildCount() > 0)
            //   item.setState(true);
        }
    }

    /**
     * Notifies all form item selection listeners about the currently
     * selected form item.
     * 
     * @param formItem the selected form item.
     */
    private void fireFormItemSelected(Object formItem) {
        for (int i = 0; i < formSelectionListeners.size(); i++)
            formSelectionListeners.get(i).onFormItemSelected(formItem);

        // some ugly hacks here...
        XformItemSelectEvent event = null;
        if (formItem instanceof FormDef) {
            event = new XformItemSelectEvent(XformItemType.FORM);
        } else if (formItem instanceof PageDef) {
            event = new XformItemSelectEvent(XformItemType.QUESTION);
        } else if (formItem instanceof QuestionDef) {
            event = new XformItemSelectEvent(XformItemType.QUESTION);
        }

        if (event != null) {
            eventBus.fireEvent(event);
        } else if (formItem == null) {
            eventBus.fireEvent(new XformListEmptyEvent());
        }
    }

    public void loadForm(FormDef formDef, boolean select, boolean langRefresh) {
        if (formDef.getId() == ModelConstants.NULL_ID)
            formDef.setId(++nextFormId);

        if (!langRefresh) {
            int count = formDef.getQuestionCount();
            if (nextQuestionId < count)
                nextQuestionId = count;

            count = formDef.getPageCount();
            if (nextPageId < count)
                nextPageId = count;

            this.formDef = formDef;

            if (formExists(formDef.getId()))
                return;

            //A temporary hack to ensure top level object is accessed.
            fireFormItemSelected(formDef);
        }

        TreeItem formRoot = null;
        if (showFormAsRoot) {
            formRoot = new CompositeTreeItem(new TreeItemWidget(images.note(), formDef.getName(), popup, this));
            formRoot.setUserObject(formDef);
            tree.addItem(formRoot);
        }

        if (formDef.getPages() != null) {
            for (int currentPageNo = 0; currentPageNo < formDef.getPages().size(); currentPageNo++) {
                TreeItem pageRoot = loadPage((PageDef) formDef.getPages().elementAt(currentPageNo), formRoot);

                //We expand only the first page.
                if (currentPageNo == 0)
                    pageRoot.setState(true);
            }
        }

        if (select && formRoot != null) {
            tree.setSelectedItem(formRoot);
            formRoot.setState(true);
        }

    }

    /**
     * Check if a form with a given id is loaded.
     * 
     * @param formId the form id.
     * @return true if it exists, else false.
     */
    public boolean formExists(int formId) {
        int count = tree.getItemCount();
        for (int index = 0; index < count; index++) {
            TreeItem item = tree.getItem(index);
            if (((FormDef) item.getUserObject()).getId() == formId) {
                tree.setSelectedItem(item);
                return true;
            }
        }

        return false;
    }

    public void refreshForm(FormDef formDef) {
        //tree.clear();
        TreeItem item = tree.getSelectedItem();
        if (item != null) {
            TreeItem root = getSelectedItemRoot(item);
            formDef.setId(((FormDef) root.getUserObject()).getId());

            tree.removeItem(root);
        }

        loadForm(formDef, true, false);
    }

    /**
     * Gets the list of forms that have been loaded.
     * 
     * @return the form list.
     */
    public List<FormDef> getForms() {
        List<FormDef> forms = new ArrayList<FormDef>();

        int count = tree.getItemCount();
        for (int index = 0; index < count; index++)
            forms.add((FormDef) tree.getItem(index).getUserObject());

        return forms;
    }

    /**
     * Loads a list of forms and selects one of them.
     * 
     * @param forms the form list to load.
     * @param selFormId the id of the form to select.
     */
    public void loadForms(List<FormDef> forms, int selFormId) {
        if (forms == null || forms.size() == 0)
            return;

        tree.clear();
        this.formDef = null;

        for (FormDef formDef : forms) {
            loadForm(formDef, formDef.getId() == selFormId, true);

            if (formDef.getId() == selFormId) {
                this.formDef = formDef;
                //A temporary hack to ensure top level object is accessed.
                fireFormItemSelected(this.formDef);
            }
        }
    }

    private TreeItem loadPage(PageDef pageDef, TreeItem formRoot) {
        TreeItem pageRoot = addImageItem(formRoot, pageDef.getName(), images.drafts(), pageDef, null);
        loadQuestions(pageDef.getQuestions(), pageRoot);
        return pageRoot;
    }

    private void loadQuestions(Vector<QuestionDef> questions, TreeItem root) {
        if (questions != null) {
            for (int currentQtnNo = 0; currentQtnNo < questions.size(); currentQtnNo++)
                loadQuestion((QuestionDef) questions.elementAt(currentQtnNo), root);
        }
    }

    private TreeItem loadQuestion(QuestionDef questionDef, TreeItem root) {
        TreeItem questionRoot = addImageItem(root, questionDef.getDisplayText(), images.lookup(), questionDef,
                questionDef.getHelpText());

        if (questionDef.getDataType() == QuestionType.LIST_EXCLUSIVE
                || questionDef.getDataType() == QuestionType.LIST_MULTIPLE) {
            List<?> options = questionDef.getOptions();
            for (int currentOptionNo = 0; currentOptionNo < options.size(); currentOptionNo++) {
                OptionDef optionDef = (OptionDef) options.get(currentOptionNo);
                addImageItem(questionRoot, optionDef.getText(), images.markRead(), optionDef, null);
            }
        } else if (questionDef.getDataType() == QuestionType.BOOLEAN) {
            addImageItem(questionRoot, i18n.displayValueTrue(), images.markRead(), null, null);
            addImageItem(questionRoot, i18n.displayValueFalse(), images.markRead(), null, null);
        } else if (questionDef.getDataType() == QuestionType.REPEAT)
            loadQuestions(questionDef.getRepeatQtnsDef().getQuestions(), questionRoot);

        return questionRoot;
    }

    /**
     * @see org.openxdata.designer.client.controller.IFormActionListener#deleteSelectedItem()
     */
    public void deleteSelectedItem() {
        TreeItem item = tree.getSelectedItem();
        if (item == null) {
            Window.alert(i18n.selectDeleteItem());
            return;
        }

        if (inReadOnlyMode() && !(item.getUserObject() instanceof FormDef))
            return;

        if (!inCutMode && !Window.confirm(i18n.deleteTreeItemPrompt()))
            return;

        deleteItem(item);
    }

    /**
     * Removes a given tree item from the tree widget.
     * 
     * @param item the tree item to delete.
     */
    private void deleteItem(TreeItem item) {
        TreeItem parent = item.getParentItem();
        int index;
        if (parent != null) {
            index = parent.getChildIndex(item);

            //If last item is the one selected, the select the previous, else the next.
            if (index == parent.getChildCount() - 1)
                index -= 1;

            removeFormDefItem(item, parent);

            //Remove the selected item.
            item.remove();

            //If no more kids, then select the parent.
            if (parent.getChildCount() == 0)
                tree.setSelectedItem(parent);
            else
                tree.setSelectedItem(parent.getChild(index));
        } else { //Must be the form root
            index = getRootItemIndex(item);
            item.remove();

            int count = tree.getItemCount();

            //If we have any items left, select the one which was after
            //the one we have just removed.
            if (count > 0) {

                //If we have deleted the last item, select the item which was before it.
                if (index == count)
                    index--;

                tree.setSelectedItem(tree.getItem(index));
            }
        }

        if (tree.getSelectedItem() == null) {
            Context.setFormDef(null);
            formDef = null;
            fireFormItemSelected(null);

            if (tree.getItemCount() == 0) {
                nextFormId = 0;
                nextOptionId = 0;
                nextPageId = 0;
                nextQuestionId = 0;
            }
        }
    }

    /**
     * Gets the index of the tree item which is at the root level.
     * 
     * @param item the tree root item whose index we are to get.
     * @return the index of the tree item.
     */
    private int getRootItemIndex(TreeItem item) {
        int count = tree.getItemCount();
        for (int index = 0; index < count; index++) {
            if (item == tree.getItem(index))
                return index;
        }

        return 0;
    }

    private void removeFormDefItem(TreeItem item, TreeItem parent) {
        Object userObj = item.getUserObject();
        Object parentUserObj = parent.getUserObject();

        if (userObj instanceof QuestionDef) {
            if (parentUserObj instanceof QuestionDef)
                ((QuestionDef) parentUserObj).getRepeatQtnsDef().removeQuestion((QuestionDef) userObj, formDef);
            else
                ((PageDef) parentUserObj).removeQuestion((QuestionDef) userObj, formDef);
        } else if (userObj instanceof OptionDef) {
            ((QuestionDef) parentUserObj).removeOption((OptionDef) userObj);
        } else if (userObj instanceof PageDef)
            ((FormDef) parentUserObj).removePage((PageDef) userObj);
    }

    /**
     * @see org.openxdata.designer.client.controller.IFormActionListener#addNewItem()
     */
    public void addNewItem() {
        if (inReadOnlyMode())
            return;

        TreeItem item = tree.getSelectedItem();

        //Check if there is any selection.
        if (item != null) {
            Object userObj = item.getUserObject();
            if (userObj instanceof QuestionDef) {
                int id = ++nextQuestionId;
                QuestionDef questionDef = new QuestionDef(id, i18n.question() + id, QuestionType.TEXT,
                        "question" + id, item.getParentItem().getUserObject());
                item = addImageItem(item.getParentItem(), questionDef.getText(), images.lookup(), questionDef,
                        questionDef.getHelpText());
                addFormDefItem(questionDef, item.getParentItem());
                tree.setSelectedItem(item);
            } else if (userObj instanceof OptionDef) {
                int id = ++nextOptionId;
                OptionDef optionDef = new OptionDef(id, i18n.option() + id, "option" + id,
                        (QuestionDef) item.getParentItem().getUserObject());
                item = addImageItem(item.getParentItem(), optionDef.getText(), images.markRead(), optionDef, null);
                addFormDefItem(optionDef, item.getParentItem());
                tree.setSelectedItem(item);
            } else if (userObj instanceof PageDef) {
                int id = ++nextPageId;
                PageDef pageDef = new PageDef(i18n.page() + id, id, null,
                        (FormDef) item.getParentItem().getUserObject());
                item = addImageItem(item.getParentItem(), pageDef.getName(), images.drafts(), pageDef, null);
                addFormDefItem(pageDef, item.getParentItem());
                tree.setSelectedItem(item);
            } else if (userObj instanceof FormDef)
                addNewForm();
        } else
            addNewForm();
    }

    /**
     * This is a hack. This method will add a new page to the current form definition
     * in context. Ideally, this entire class needs to be refactored. There are traces
     * of controller, view and model responsibilities in this class.
     * Also, instead of trying to pick out elements from the tree widget itself, a selection model 
     * of some sort should be used. That would make this entire process less hideous
     */
    public void addNewPage() {
        if (inReadOnlyMode()) {
            return;
        }

        TreeItem item = tree.getSelectedItem();

        TreeItem parentItem = null;
        FormDef formDef = null;

        Object userObj = item.getUserObject();

        // figure out which item is selected in the form definition list tree
        if (userObj instanceof PageDef) {
            parentItem = item.getParentItem();
            formDef = ((PageDef) userObj).getParent();
        } else if (userObj instanceof OptionDef) {
            parentItem = item.getParentItem().getParentItem().getParentItem(); // yikes!
            formDef = ((OptionDef) userObj).getParent().getParentFormDef();
        } else if (userObj instanceof QuestionDef) {
            parentItem = item.getParentItem().getParentItem();
            formDef = ((QuestionDef) userObj).getParentFormDef();
        } else if (userObj instanceof FormDef) {
            parentItem = item;
            formDef = (FormDef) userObj;
        }

        // first add the page
        int pageId = ++nextPageId;
        PageDef pageDef = new PageDef(i18n.page() + pageId, pageId, null, formDef);
        TreeItem pageDefItem = addImageItem(parentItem, pageDef.getName(), images.drafts(), pageDef, null);
        addFormDefItem(pageDef, parentItem);

        // now the question
        int quesId = ++nextQuestionId;
        QuestionDef questionDef = new QuestionDef(quesId, i18n.question() + quesId, QuestionType.TEXT,
                "question" + quesId, pageDef);
        TreeItem quesDefItem = addImageItem(pageDefItem, questionDef.getText(), images.lookup(), questionDef,
                questionDef.getHelpText());
        addFormDefItem(questionDef, pageDefItem);
        tree.setSelectedItem(quesDefItem);

        // make sure to open up the PageDef tree item
        pageDefItem.setState(true, true);
    }

    public void addNewQuestion(QuestionType dataType) {
        if (inReadOnlyMode())
            return;

        TreeItem item = tree.getSelectedItem();

        //Check if there is any selection.
        if (item != null) {
            Object userObj = item.getUserObject();
            if (userObj instanceof QuestionDef) {
                int id = ++nextQuestionId;
                QuestionDef questionDef = new QuestionDef(id, i18n.question() + id, QuestionType.TEXT,
                        "question" + id, item.getParentItem().getUserObject());
                questionDef.setDataType(dataType);
                item = addImageItem(item.getParentItem(), questionDef.getText(), images.lookup(), questionDef,
                        questionDef.getHelpText());
                addFormDefItem(questionDef, item.getParentItem());

                if (dataType == QuestionType.LIST_EXCLUSIVE || dataType == QuestionType.LIST_MULTIPLE)
                    addNewOptionDef(questionDef, item);

                tree.setSelectedItem(item);
            } else if (userObj instanceof OptionDef) {
                int id = ++nextQuestionId;
                QuestionDef questionDef = new QuestionDef(id, i18n.question() + id, QuestionType.TEXT,
                        "question" + id, item.getParentItem().getParentItem().getUserObject());
                questionDef.setDataType(dataType);
                item = addImageItem(item.getParentItem().getParentItem(), questionDef.getText(), images.lookup(),
                        questionDef, questionDef.getHelpText());
                addFormDefItem(questionDef, item.getParentItem());

                if (dataType == QuestionType.LIST_EXCLUSIVE || dataType == QuestionType.LIST_MULTIPLE)
                    addNewOptionDef(questionDef, item);

                tree.setSelectedItem(item);
            } else if (userObj instanceof PageDef) {
                int id = ++nextQuestionId;
                QuestionDef questionDef = new QuestionDef(id, i18n.question() + id, QuestionType.TEXT,
                        "question" + id, item.getUserObject());
                questionDef.setDataType(dataType);
                item = addImageItem(item, questionDef.getText(), images.lookup(), questionDef,
                        questionDef.getHelpText());
                addFormDefItem(questionDef, item.getParentItem());

                if (dataType == QuestionType.LIST_EXCLUSIVE || dataType == QuestionType.LIST_MULTIPLE)
                    addNewOptionDef(questionDef, item);

                tree.setSelectedItem(item);
            } else if (userObj instanceof FormDef) {
                //addNewForm();

                //If not yet got pages, just quit.
                if (item.getChildCount() == 0)
                    return;

                TreeItem parentItem = item.getChild(0);

                int id = ++nextQuestionId;
                QuestionDef questionDef = new QuestionDef(id, i18n.question() + id, QuestionType.TEXT,
                        "question" + id, parentItem.getUserObject());
                questionDef.setDataType(dataType);
                item = addImageItem(parentItem, questionDef.getText(), images.lookup(), questionDef,
                        questionDef.getHelpText());
                addFormDefItem(questionDef, item.getParentItem());

                if (dataType == QuestionType.LIST_EXCLUSIVE || dataType == QuestionType.LIST_MULTIPLE)
                    addNewOptionDef(questionDef, item);

                tree.setSelectedItem(item);
            }
        } else {
            addNewForm();
            item = tree.getSelectedItem();
            QuestionDef questionDef = (QuestionDef) item.getUserObject();
            questionDef.setDataType(dataType);

            if (dataType == QuestionType.LIST_EXCLUSIVE || dataType == QuestionType.LIST_MULTIPLE)
                addNewOptionDef(questionDef, item);

            tree.setSelectedItem(item.getParentItem());
            tree.setSelectedItem(item);
        }
    }

    private void addNewOptionDef(QuestionDef questionDef, TreeItem parentItem) {
        int id = ++nextOptionId;
        OptionDef optionDef = new OptionDef(id, i18n.option() + id, "option" + id, questionDef);
        addImageItem(parentItem, optionDef.getText(), images.markRead(), optionDef, null);
        addFormDefItem(optionDef, parentItem);

        parentItem.setState(true);
    }

    private void addFormDefItem(Object obj, TreeItem parentItem) {
        Object parentUserObj = parentItem.getUserObject();
        if (parentUserObj instanceof QuestionDef) {
            if (obj instanceof OptionDef)
                ((QuestionDef) parentUserObj).addOption((OptionDef) obj);
            else
                ((QuestionDef) parentUserObj).getRepeatQtnsDef().addQuestion((QuestionDef) obj);
        } else if (parentUserObj instanceof PageDef)
            ((PageDef) parentUserObj).addQuestion((QuestionDef) obj);
        else if (parentUserObj instanceof FormDef)
            ((FormDef) parentUserObj).addPage((PageDef) obj);

    }

    public void addNewForm() {
        int id = ++nextFormId;
        addNewForm(i18n.newForm() + id, "new_form" + id, id);

        //Automatically add a new page
        addNewChildItem(false);

        //Automatically add a new question
        addNewChildItem(false);
    }

    public void addNewForm(String name, String varName, int formId) {
        if (inReadOnlyMode())
            return;

        if (formExists(formId))
            return;

        FormDef formDef = new FormDef(formId, name, varName, varName, null, null, null, null, null, null);
        TreeItem item = new CompositeTreeItem(new TreeItemWidget(images.note(), formDef.getName(), popup, this));
        item.setUserObject(formDef);
        tree.addItem(item);
        tree.setSelectedItem(item);
    }

    /**
     * @see org.openxdata.designer.client.controller.IFormActionListener#addNewChildItem()
     */
    public void addNewChildItem() {
        addNewChildItem(true);
    }

    /**
     * Adds a new child item.
     */
    public void addNewChildItem(boolean addNewIfNoKids) {
        if (inReadOnlyMode())
            return;

        TreeItem item = tree.getSelectedItem();

        //Check if there is any selection.
        if (item == null) {
            if (addNewIfNoKids)
                addNewItem();
            return;
        }

        Object userObj = item.getUserObject();
        if (userObj instanceof PageDef || (userObj instanceof QuestionDef
                && ((QuestionDef) userObj).getDataType() == QuestionType.REPEAT)) {

            int id = ++nextQuestionId;
            QuestionDef questionDef = new QuestionDef(id, i18n.question() + id, QuestionType.TEXT, "question" + id,
                    userObj);
            item = addImageItem(item, questionDef.getText(), images.lookup(), questionDef,
                    questionDef.getHelpText());
            addFormDefItem(questionDef, item.getParentItem());
            tree.setSelectedItem(item);
            item.getParentItem().setState(true);
        } else if (userObj instanceof QuestionDef
                && (((QuestionDef) userObj).getDataType() == QuestionType.LIST_EXCLUSIVE
                        || ((QuestionDef) userObj).getDataType() == QuestionType.LIST_MULTIPLE)) {

            int id = ++nextOptionId;
            OptionDef optionDef = new OptionDef(id, i18n.option() + id, "option" + id, (QuestionDef) userObj);
            item = addImageItem(item, optionDef.getText(), images.markRead(), optionDef, null);
            addFormDefItem(optionDef, item.getParentItem());
            tree.setSelectedItem(item);
            item.getParentItem().setState(true);
        } else if (userObj instanceof FormDef) {
            int id = ++nextPageId;
            PageDef pageDef = new PageDef(i18n.page() + id, id, null, (FormDef) userObj);
            item = addImageItem(item, pageDef.getName(), images.drafts(), pageDef, null);
            addFormDefItem(pageDef, item.getParentItem());
            tree.setSelectedItem(item);
            item.getParentItem().setState(true);

            //Automatically add a new question
            addNewChildItem(false);
        } else if (addNewIfNoKids)
            addNewItem();
    }

    /**
     * @see org.openxdata.designer.client.controller.IFormActionListener#moveItemUp()
     */
    public void moveItemUp() {
        if (inReadOnlyMode())
            return;

        TreeItem item = tree.getSelectedItem();

        //Check if there is any selection.
        if (item == null)
            return;

        TreeItem parent = item.getParentItem();

        //We don't move root node (which has no parent, that is the form itself, since we design one form at a time)
        if (parent == null)
            return;

        //One item can't move against itself.
        int count = parent.getChildCount();
        if (count == 1)
            return;

        int index = parent.getChildIndex(item);
        if (index == 0)
            return; //Can't move any further upwards.

        //move the item in the form object model.
        moveFormItemUp(item, parent);

        TreeItem currentItem; // = parent.getChild(index - 1);
        List<TreeItem> list = new ArrayList<TreeItem>();

        item.remove();

        while (parent.getChildCount() >= index) {
            currentItem = parent.getChild(index - 1);
            list.add(currentItem);
            currentItem.remove();
        }

        parent.addItem(item);
        for (int i = 0; i < list.size(); i++)
            parent.addItem((TreeItem) list.get(i));

        tree.setSelectedItem(item);
    }

    private void moveFormItemUp(TreeItem item, TreeItem parent) {
        Object userObj = item.getUserObject();
        Object parentObj = parent.getUserObject();

        //Normal question
        if (userObj instanceof QuestionDef && parentObj instanceof PageDef)
            ((PageDef) parentObj).moveQuestionUp((QuestionDef) userObj);
        else if (userObj instanceof QuestionDef && parentObj instanceof QuestionDef)
            ((QuestionDef) parentObj).getRepeatQtnsDef().moveQuestionUp((QuestionDef) userObj);
        else if (userObj instanceof PageDef)
            ((FormDef) parentObj).movePageUp((PageDef) userObj);
        else if (userObj instanceof OptionDef)
            ((QuestionDef) parentObj).moveOptionUp((OptionDef) userObj);
    }

    private void moveFormItemDown(TreeItem item, TreeItem parent) {
        Object userObj = item.getUserObject();
        Object parentObj = parent.getUserObject();

        //Normal question
        if (userObj instanceof QuestionDef && parentObj instanceof PageDef)
            ((PageDef) parentObj).moveQuestionDown((QuestionDef) userObj);
        else if (userObj instanceof QuestionDef && parentObj instanceof QuestionDef)
            ((QuestionDef) parentObj).getRepeatQtnsDef().moveQuestionDown((QuestionDef) userObj);
        else if (userObj instanceof PageDef)
            ((FormDef) parentObj).movePageDown((PageDef) userObj);
        else if (userObj instanceof OptionDef)
            ((QuestionDef) parentObj).moveOptionDown((OptionDef) userObj);
    }

    /**
     * @see org.openxdata.designer.client.controller.IFormActionListener#moveItemDown()
     */
    public void moveItemDown() {
        if (inReadOnlyMode())
            return;

        TreeItem item = tree.getSelectedItem();

        //Check if there is any selection.
        if (item == null)
            return;

        TreeItem parent = item.getParentItem();

        //We don't move root node (which has no parent, that is the form itself, since we design one form at a time)
        if (parent == null)
            return;

        //One item can't move against itself.
        int count = parent.getChildCount();
        if (count == 1)
            return;

        int index = parent.getChildIndex(item);
        if (index == count - 1)
            return; //Can't move any further downwards.

        //move the item in the form object model.
        moveFormItemDown(item, parent);

        TreeItem currentItem; // = parent.getChild(index - 1);
        List<TreeItem> list = new ArrayList<TreeItem>();

        item.remove();

        while (parent.getChildCount() > 0 && parent.getChildCount() > index) {
            currentItem = parent.getChild(index);
            list.add(currentItem);
            currentItem.remove();
        }

        for (int i = 0; i < list.size(); i++) {
            if (i == 1)
                parent.addItem(item); //Add after the first item.
            parent.addItem((TreeItem) list.get(i));
        }

        if (list.size() == 1)
            parent.addItem(item);

        tree.setSelectedItem(item);
    }

    /**
     * @see org.openxdata.designer.client.controller.IFormChangeListener#onFormItemChanged(java.lang.Object)
     */
    public Object onFormItemChanged(Object formItem) {
        TreeItem item = tree.getSelectedItem();
        if (item == null)
            return formItem; //How can this happen?

        if (item.getUserObject() != formItem)
            return formItem;

        if (formItem instanceof QuestionDef) {
            QuestionDef questionDef = (QuestionDef) formItem;
            item.setWidget(new TreeItemWidget(images.lookup(), questionDef.getDisplayText(), popup, this));
            item.setTitle(questionDef.getHelpText());
        } else if (formItem instanceof OptionDef) {
            OptionDef optionDef = (OptionDef) formItem;
            item.setWidget(new TreeItemWidget(images.markRead(), optionDef.getText(), popup, this));
        } else if (formItem instanceof PageDef) {
            PageDef pageDef = (PageDef) formItem;
            item.setWidget(new TreeItemWidget(images.drafts(), pageDef.getName(), popup, this));
        } else if (formItem instanceof FormDef) {
            FormDef formDef = (FormDef) formItem;
            item.setWidget(new TreeItemWidget(images.note(), formDef.getName(), popup, this));
        }

        return formItem;
    }

    /**
     * @see org.openxdata.designer.client.controller.IFormChangeListener#onDeleteChildren(Object)
     */
    public void onDeleteChildren(Object formItem) {
        TreeItem item = tree.getSelectedItem();
        if (item == null)
            return; //How can this happen?

        if (formItem instanceof QuestionDef) {
            while (item.getChildCount() > 0)
                deleteItem(item.getChild(0));
        }
    }

    /**
     * @see org.openxdata.designer.client.controller.IFormActionListener#cutItem()
     */
    public void cutItem() {
        if (inReadOnlyMode())
            return;

        TreeItem item = tree.getSelectedItem();
        if (item == null)
            return;

        clipboardItem = item.getUserObject();

        inCutMode = true;
        deleteSelectedItem();
        inCutMode = false;
    }

    /**
     * @see org.openxdata.designer.client.controller.IFormActionListener#copyItem()
     */
    public void copyItem() {
        TreeItem item = tree.getSelectedItem();
        if (item == null)
            return;

        clipboardItem = item.getUserObject();
    }

    /**
     * @see org.openxdata.designer.client.controller.IFormActionListener#pasteItem()
     */
    public void pasteItem() {
        if (inReadOnlyMode())
            return;

        //Check if we have anything in the clipboard.
        if (clipboardItem == null)
            return;

        TreeItem item = tree.getSelectedItem();
        if (item == null)
            return;

        Object userObj = item.getUserObject();

        if (clipboardItem instanceof QuestionDef) {
            //Questions can be pasted only as kids of pages or repeat questions.
            if (!((userObj instanceof PageDef) || (userObj instanceof QuestionDef
                    && ((QuestionDef) userObj).getDataType() == QuestionType.REPEAT))) {
                return;
            }

            //create a copy of the clipboard question.
            QuestionDef questionDef = new QuestionDef((QuestionDef) clipboardItem, userObj);

            //Repeat question can only be child of a page but not another question.
            if (questionDef.getDataType() == QuestionType.REPEAT && userObj instanceof QuestionDef)
                return;

            questionDef.setId(item.getChildCount() + 1);

            if (userObj instanceof PageDef)
                ((PageDef) userObj).addQuestion(questionDef);
            else
                ((QuestionDef) userObj).getRepeatQtnsDef().addQuestion(questionDef);

            item = loadQuestion(questionDef, item);

            tree.setSelectedItem(item);
            item.getParentItem().setState(true);
            item.setState(true);
        } else if (clipboardItem instanceof PageDef) {
            //Pages can be pasted only as kids of forms.
            if (!(userObj instanceof FormDef))
                return;

            //create a copy of the clipboard page.
            PageDef pageDef = new PageDef((PageDef) clipboardItem, (FormDef) userObj);

            pageDef.setPageNo(item.getChildCount() + 1);
            ((FormDef) userObj).addPage(pageDef);
            item = loadPage(pageDef, item);

            tree.setSelectedItem(item);
            item.getParentItem().setState(true);
            item.setState(true);
        } else if (clipboardItem instanceof OptionDef) {
            //Question options can be pasted only as kids of single and multi select questions.
            if (!(userObj instanceof QuestionDef
                    && (((QuestionDef) userObj).getDataType() == QuestionType.LIST_EXCLUSIVE)
                    || ((QuestionDef) userObj).getDataType() == QuestionType.LIST_MULTIPLE))
                return;

            //         create a copy of the clipboard page.
            OptionDef optionDef = new OptionDef((OptionDef) clipboardItem, (QuestionDef) userObj);
            optionDef.setId(item.getChildCount() + 1);
            ((QuestionDef) userObj).addOption(optionDef);
            item = addImageItem(item, optionDef.getText(), images.markRead(), optionDef, null);

            tree.setSelectedItem(item);
            item.getParentItem().setState(true);
            item.setState(true);
        }
    }

    /**
     * @see org.openxdata.designer.client.controller.IFormDesignerListener#refresh(Object)
     */
    public void refreshItem() {
        if (inReadOnlyMode())
            return;

        formDesignerListener.refresh(this);
    }

    /**
     * @see org.openxdata.designer.client.controller.IFormDesignerListener#saveForm()
     */
    public void saveItem() {
        formDesignerListener.saveForm();
    }

    /**
     * Gets the selected form.
     * 
     * @return the selected form.
     */
    public FormDef getSelectedForm() {
        TreeItem item = tree.getSelectedItem();
        if (item != null)
            return getSelectedForm(item);
        return null;
    }

    /**
     * Gets the form to which the selected tree item belongs.
     * 
     * @param item the tree item.
     * @return the form definition object.
     */
    private FormDef getSelectedForm(TreeItem item) {
        Object obj = item.getUserObject();
        if (obj instanceof FormDef)
            return (FormDef) obj;
        return getSelectedForm(item.getParentItem());
    }

    private TreeItem getSelectedItemRoot(TreeItem item) {
        if (item == null)
            return null;

        if (item.getParentItem() == null)
            return item;
        return getSelectedItemRoot(item.getParentItem());
    }

    /**
     * Removes all forms.
     */
    public void clear() {
        tree.clear();
    }

    /**
     * Checks if the selected form is valid for saving.
     * 
     * @return true if valid, else false.
     */
    public boolean isValidForm() {
        TreeItem parent = getSelectedItemRoot(tree.getSelectedItem());
        if (parent == null)
            return true;

        Map<String, String> pageNos = new HashMap<String, String>();
        Map<String, QuestionDef> bindings = new HashMap<String, QuestionDef>();
        int count = parent.getChildCount();
        for (int index = 0; index < count; index++) {
            TreeItem child = parent.getChild(index);
            PageDef pageDef = (PageDef) child.getUserObject();
            String pageNo = String.valueOf(pageDef.getPageNo());
            if (pageNos.containsKey(pageNo)) {
                tree.setSelectedItem(child);
                tree.ensureSelectedItemVisible();
                Window.alert(i18n.selectedPage() + pageDef.getName() + i18n.shouldNotSharePageBinding()
                        + pageNos.get(pageNo) + "]");
                return false;
            } else
                pageNos.put(pageNo, pageDef.getName());

            if (!isValidQuestionList(child, bindings))
                return false;
        }

        return true;
    }

    private boolean isValidQuestionList(TreeItem parent, Map<String, QuestionDef> bindings) {
        int count = parent.getChildCount();
        for (int index = 0; index < count; index++) {
            TreeItem child = parent.getChild(index);
            QuestionDef questionDef = (QuestionDef) child.getUserObject();
            String variableName = questionDef.getBinding();
            if (bindings.containsKey(
                    variableName) /*&& questionDef.getParent() == bindings.get(variableName).getParent()*/) {
                tree.setSelectedItem(child);
                tree.ensureSelectedItemVisible();
                Window.alert(i18n.selectedQuestion() + questionDef.getText() + i18n.shouldNotShareQuestionBinding()
                        + bindings.get(variableName).getDisplayText() + "]");
                return false;
            } else
                bindings.put(variableName, questionDef);

            if (questionDef.getDataType() == QuestionType.REPEAT) {
                if (!isValidQuestionList(child, bindings))
                    return false;
            } else if (questionDef.getDataType() == QuestionType.LIST_EXCLUSIVE
                    || questionDef.getDataType() == QuestionType.LIST_MULTIPLE) {
                if (!isValidOptionList(child))
                    return false;
            }
        }

        return true;
    }

    private boolean isValidOptionList(TreeItem parent) {
        Map<String, String> bindings = new HashMap<String, String>();

        int count = parent.getChildCount();
        for (int index = 0; index < count; index++) {
            TreeItem child = parent.getChild(index);
            OptionDef optionDef = (OptionDef) child.getUserObject();
            String variableName = optionDef.getVariableName();
            if (bindings.containsKey(variableName)) {
                tree.setSelectedItem(child);
                tree.ensureSelectedItemVisible();
                Window.alert(i18n.selectedOption() + optionDef.getText() + i18n.shouldNotShareOptionBinding()
                        + bindings.get(variableName) + "]");
                return false;
            } else
                bindings.put(variableName, optionDef.getText());
        }
        return true;
    }

    /**
     * @see org.openxdata.designer.client.controller.IFormActionListener#moveUp()
     */
    public void moveUp() {
        TreeItem item = tree.getSelectedItem();
        if (item == null)
            return;

        int index;
        TreeItem parent = item.getParentItem();
        if (parent == null) {
            index = getRootItemIndex(parent);
            if (index == 0)
                return;
            tree.setSelectedItem(tree.getItem(index - 1));
        } else {
            index = parent.getChildIndex(item);
            if (index == 0)
                return;
            tree.setSelectedItem(parent.getChild(index - 1));
        }
    }

    /**
     * @see org.openxdata.designer.client.controller.IFormActionListener#moveDown()
     */
    public void moveDown() {
        TreeItem item = tree.getSelectedItem();
        if (item == null)
            return;

        int index;
        TreeItem parent = item.getParentItem();
        if (parent == null) {
            index = getRootItemIndex(parent);
            if (index == tree.getItemCount() - 1)
                return;
            tree.setSelectedItem(tree.getItem(index + 1));
        } else {
            index = parent.getChildIndex(item);
            if (index == parent.getChildCount() - 1)
                return;
            tree.setSelectedItem(parent.getChild(index + 1));
        }
    }

    /**
     * Selected the parent of the selected item.
     */
    public void moveToParent() {
        TreeItem item = tree.getSelectedItem();
        if (item == null)
            return;

        TreeItem parent = item.getParentItem();
        if (parent == null)
            return;

        tree.setSelectedItem(parent);
        tree.ensureSelectedItemVisible();
    }

    /**
     * Selects the child of the selected item.
     */
    public void moveToChild() {
        TreeItem item = tree.getSelectedItem();
        if (item == null)
            return;

        if (item.getChildCount() == 0) {
            addNewChildItem(false);
            return;
        }

        TreeItem child = item.getChild(0);
        tree.setSelectedItem(child);
        tree.ensureSelectedItemVisible();
    }

    /**
     * Checks if the selected form is in read only mode. In read only mode
     * we can only change the text and help text of items.
     * 
     * @return true if in read only mode, else false.
     */
    private boolean inReadOnlyMode() {
        return Context.isStructureReadOnly();
    }
}