org.openelis.modules.attachment.client.AttachmentScreenUI.java Source code

Java tutorial

Introduction

Here is the source code for org.openelis.modules.attachment.client.AttachmentScreenUI.java

Source

/**
 * Exhibit A - UIRF Open-source Based Public Software License.
 * 
 * The contents of this file are subject to the UIRF Open-source Based Public
 * Software License(the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * openelis.uhl.uiowa.edu
 * 
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
 * the specific language governing rights and limitations under the License.
 * 
 * The Original Code is OpenELIS code.
 * 
 * The Initial Developer of the Original Code is The University of Iowa.
 * Portions created by The University of Iowa are Copyright 2006-2008. All
 * Rights Reserved.
 * 
 * Contributor(s): ______________________________________.
 * 
 * Alternatively, the contents of this file marked "Separately-Licensed" may be
 * used under the terms of a UIRF Software license ("UIRF Software License"), in
 * which case the provisions of a UIRF Software License are applicable instead
 * of those above.
 */
package org.openelis.modules.attachment.client;

import static org.openelis.modules.main.client.Logger.*;
import static org.openelis.ui.screen.Screen.ShortKeys.*;
import static org.openelis.ui.screen.State.*;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.logging.Level;

import org.openelis.cache.SectionCache;
import org.openelis.cache.UserCache;
import org.openelis.constants.Messages;
import org.openelis.domain.AttachmentDO;
import org.openelis.domain.AttachmentIssueViewDO;
import org.openelis.domain.SectionDO;
import org.openelis.manager.AttachmentManager;
import org.openelis.meta.AttachmentMeta;
import org.openelis.ui.common.Datetime;
import org.openelis.ui.common.EntityLockedException;
import org.openelis.ui.common.SectionPermission.SectionFlags;
import org.openelis.ui.common.SystemUserPermission;
import org.openelis.ui.common.ValidationErrorsList;
import org.openelis.ui.common.data.Query;
import org.openelis.ui.event.BeforeCloseEvent;
import org.openelis.ui.event.BeforeCloseHandler;
import org.openelis.ui.event.StateChangeEvent;
import org.openelis.ui.screen.AsyncCallbackUI;
import org.openelis.ui.screen.Screen;
import org.openelis.ui.screen.ScreenHandler;
import org.openelis.ui.widget.Button;
import org.openelis.ui.widget.Confirm;
import org.openelis.ui.widget.Dropdown;
import org.openelis.ui.widget.Item;
import org.openelis.ui.widget.TextBox;
import org.openelis.ui.widget.WindowInt;
import org.openelis.ui.widget.calendar.Calendar;
import org.openelis.ui.widget.fileupload.DropIndicator;
import org.openelis.ui.widget.fileupload.FileDrop;
import org.openelis.ui.widget.table.event.BeforeCellEditedEvent;
import org.openelis.ui.widget.table.event.BeforeCellEditedHandler;
import org.openelis.ui.widget.table.event.CellEditedEvent;
import org.openelis.ui.widget.table.event.CellEditedHandler;
import org.openelis.ui.widget.tree.Node;
import org.openelis.ui.widget.tree.Tree;

import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.DoubleClickEvent;
import com.google.gwt.event.dom.client.DoubleClickHandler;
import com.google.gwt.event.dom.client.DragEnterEvent;
import com.google.gwt.event.dom.client.DragEnterHandler;
import com.google.gwt.event.dom.client.DragLeaveEvent;
import com.google.gwt.event.dom.client.DragLeaveHandler;
import com.google.gwt.event.dom.client.DropEvent;
import com.google.gwt.event.logical.shared.BeforeSelectionEvent;
import com.google.gwt.event.logical.shared.BeforeSelectionHandler;
import com.google.gwt.event.logical.shared.SelectionEvent;
import com.google.gwt.event.logical.shared.SelectionHandler;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.uibinder.client.UiHandler;
import com.google.gwt.uibinder.client.UiTemplate;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.LayoutPanel;
import com.google.gwt.user.client.ui.Widget;

public class AttachmentScreenUI extends Screen {

    @UiTemplate("Attachment.ui.xml")
    interface AttachmentUiBinder extends UiBinder<Widget, AttachmentScreenUI> {
    };

    public static final AttachmentUiBinder uiBinder = GWT.create(AttachmentUiBinder.class);

    protected AttachmentManager manager, previousManager;

    protected HashMap<Integer, AttachmentManager> managers;

    protected ArrayList<AttachmentManager> deleteManagers;

    @UiField
    protected Calendar createdDate;

    @UiField
    protected TextBox<String> description, issueText;

    @UiField
    protected Dropdown<Integer> querySection, tableSection;

    @UiField
    protected Tree tree;

    @UiField
    protected Button searchButton, attachButton, updateButton, deleteButton, commitButton, abortButton;

    protected AttachmentScreenUI screen;

    protected FileDrop fileDrop;

    protected DropIndicator dropIndicator;

    protected Confirm confirm;

    protected Query query;

    protected AsyncCallbackUI<ArrayList<AttachmentManager>> queryCall;

    protected AsyncCallbackUI<AttachmentManager> fetchForUpdateCall, updateCall, unlockCall;

    protected AsyncCallback<Void> deleteCall;

    protected boolean isNewQuery, isLoadedFromQuery, closeHandlerAdded;

    protected static int ROWS_PER_PAGE = 19;

    protected static String ATTACHMENT_LEAF = "attachment", ATTACHMENT_ITEM_LEAF = "attachmentItem",
            CLICK_FOR_MORE_LEAF = "clickForMore";

    public AttachmentScreenUI(WindowInt window) throws Exception {
        setWindow(window);

        initWidget(uiBinder.createAndBindUi(this));

        initialize();
        setState(QUERY);
        loadTree(null, true, true, false);

        logger.fine("Attachment Screen Opened");
    }

    public AttachmentScreenUI() throws Exception {
        initWidget(uiBinder.createAndBindUi(this));

        initialize();
        setState(QUERY);
        loadTree(null, true, true, false);

        logger.fine("Attachment Screen Opened");
    }

    protected void initialize() {
        ArrayList<Item<Integer>> model1, model2;

        screen = this;

        /*
         * screen fields and widgets
         */
        addScreenHandler(description, AttachmentMeta.getDescription(), new ScreenHandler<String>() {
            public void onStateChange(StateChangeEvent event) {
                description.setEnabled(isState(QUERY, DISPLAY));
                description.setQueryMode(isState(QUERY, DISPLAY));
            }

            public Widget onTab(boolean forward) {
                return forward ? createdDate : abortButton;
            }
        });

        addScreenHandler(createdDate, AttachmentMeta.getCreatedDate(), new ScreenHandler<Datetime>() {
            public void onStateChange(StateChangeEvent event) {
                createdDate.setEnabled(isState(QUERY, DISPLAY));
                createdDate.setQueryMode(isState(QUERY, DISPLAY));
            }

            public Widget onTab(boolean forward) {
                return forward ? querySection : description;
            }
        });

        addScreenHandler(querySection, AttachmentMeta.getSectionId(), new ScreenHandler<Integer>() {
            public void onStateChange(StateChangeEvent event) {
                querySection.setEnabled(isState(QUERY, DISPLAY));
                querySection.setQueryMode(isState(QUERY, DISPLAY));
            }

            public Widget onTab(boolean forward) {
                return forward ? issueText : createdDate;
            }
        });

        addScreenHandler(issueText, AttachmentMeta.getIssueText(), new ScreenHandler<String>() {
            public void onStateChange(StateChangeEvent event) {
                issueText.setEnabled(isState(QUERY, DISPLAY));
                issueText.setQueryMode(isState(QUERY, DISPLAY));
            }

            public Widget onTab(boolean forward) {
                return forward ? searchButton : querySection;
            }
        });

        addScreenHandler(searchButton, "searchButton", new ScreenHandler<Object>() {
            public void onStateChange(StateChangeEvent event) {
                searchButton.setEnabled(isState(QUERY, DISPLAY));
            }

            public Widget onTab(boolean forward) {
                return forward ? tree : issueText;
            }
        });

        addScreenHandler(tree, "tree", new ScreenHandler<Object>() {
            public void onStateChange(StateChangeEvent event) {
                tree.setEnabled(true);
            }

            public Widget onTab(boolean forward) {
                return forward ? attachButton : searchButton;
            }
        });

        tree.addBeforeSelectionHandler(new BeforeSelectionHandler<Integer>() {
            @Override
            public void onBeforeSelection(BeforeSelectionEvent<Integer> event) {
                Integer id;
                Node node;

                if (!isState(UPDATE))
                    return;

                /*
                 * in Update state, only the nodes that belong to the locked
                 * attachment can be selected
                 */
                node = tree.getNodeAt(event.getItem());

                if (ATTACHMENT_LEAF.equals(node.getType()))
                    id = node.getData();
                else if (ATTACHMENT_ITEM_LEAF.equals(node.getType()))
                    id = node.getParent().getData();
                else
                    id = null;

                if (!manager.getAttachment().getId().equals(id))
                    event.cancel();
            }
        });

        tree.addSelectionHandler(new SelectionHandler<Integer>() {
            @Override
            public void onSelection(SelectionEvent<Integer> event) {
                nodeSelected(event.getSelectedItem());
            }
        });

        tree.addDoubleClickHandler(new DoubleClickHandler() {
            @Override
            public void onDoubleClick(DoubleClickEvent event) {
                if (!isState(UPDATE, DELETE))
                    displayAttachment(tree.getNodeAt(tree.getSelectedNode()), null);
            }
        });

        tree.addBeforeCellEditedHandler(new BeforeCellEditedHandler() {
            @Override
            public void onBeforeCellEdited(BeforeCellEditedEvent event) {
                String name;
                Integer sectId;
                SystemUserPermission perm;
                Node node;

                node = tree.getNodeAt(tree.getSelectedNode());
                if (!ATTACHMENT_LEAF.equals(node.getType()) || !isState(UPDATE)
                        || (event.getCol() != 0 && event.getCol() != 2)) {
                    event.cancel();
                    return;
                }

                perm = UserCache.getPermission();
                sectId = manager.getAttachment().getSectionId();
                try {
                    if (sectId != null) {
                        /*
                         * allow changing any field of the attachment only if
                         * the user has complete permission to the current
                         * section
                         */
                        name = SectionCache.getById(sectId).getName();
                        if (!perm.has(name, SectionFlags.COMPLETE)) {
                            event.cancel();
                            return;
                        }
                    }

                    /*
                     * make sure that the section can't be changed to one that
                     * the user doesn't have complete permission to
                     */
                    for (Item<Integer> row : tableSection.getModel()) {
                        name = SectionCache.getById(row.getKey()).getName();
                        row.setEnabled(perm.has(name, SectionFlags.COMPLETE));
                    }
                } catch (Exception e) {
                    Window.alert(e.getMessage());
                    logger.log(Level.SEVERE, e.getMessage(), e);
                    event.cancel();
                }
            }
        });

        tree.addCellEditedHandler(new CellEditedHandler() {
            public void onCellUpdated(CellEditedEvent event) {
                Object val;
                AttachmentDO data;

                val = tree.getValueAt(event.getRow(), event.getCol());
                data = manager.getAttachment();
                switch (event.getCol()) {
                case 0:
                    data.setDescription((String) val);
                    break;
                case 2:
                    data.setSectionId((Integer) val);
                    break;
                }
            }
        });

        tree.setAllowMultipleSelection(true);

        addScreenHandler(attachButton, "attachButton", new ScreenHandler<Object>() {
            public void onStateChange(StateChangeEvent event) {
                attachButton.setEnabled(false);
            }

            public Widget onTab(boolean forward) {
                return forward ? updateButton : tree;
            }
        });

        addScreenHandler(updateButton, "updateButton", new ScreenHandler<Object>() {
            public void onStateChange(StateChangeEvent event) {
                updateButton.setEnabled(isState(DISPLAY, UPDATE) && manager != null);
                if (isState(UPDATE)) {
                    updateButton.lock();
                    updateButton.setPressed(true);
                }
            }

            public Widget onTab(boolean forward) {
                return forward ? deleteButton : tree;
            }
        });

        addShortcut(updateButton, 'u', CTRL);

        addScreenHandler(deleteButton, "deleteButton", new ScreenHandler<Object>() {
            public void onStateChange(StateChangeEvent event) {
                deleteButton.setEnabled(isState(DISPLAY, DELETE) && manager != null);
                if (isState(DELETE)) {
                    deleteButton.lock();
                    deleteButton.setPressed(true);
                }
            }

            public Widget onTab(boolean forward) {
                return forward ? commitButton : updateButton;
            }
        });

        addShortcut(deleteButton, 'd', CTRL);

        addScreenHandler(commitButton, "commitButton", new ScreenHandler<Object>() {
            public void onStateChange(StateChangeEvent event) {
                commitButton.setEnabled(isState(UPDATE, DELETE));
            }

            public Widget onTab(boolean forward) {
                return forward ? deleteButton : abortButton;
            }
        });

        addShortcut(commitButton, 'm', CTRL);

        addScreenHandler(abortButton, "abortButton", new ScreenHandler<Object>() {
            public void onStateChange(StateChangeEvent event) {
                abortButton.setEnabled(isState(UPDATE, DELETE));
            }

            public Widget onTab(boolean forward) {
                return forward ? description : commitButton;
            }
        });

        addShortcut(abortButton, 'o', CTRL);

        fileDrop = new FileDrop(screen, "openelis/upload") {
            public void onDrop(DropEvent event) {
                if (!fileDrop.isEnabled())
                    return;

                if (dropIndicator != null) {
                    dropIndicator.removeFromParent();
                    dropIndicator = null;
                }

                super.onDrop(event);
            }

            public void onDropSuccess() {
                ArrayList<AttachmentManager> ams;

                try {
                    /*
                     * don't let a file be dropped for upload if a record is
                     * locked
                     */
                    if (isState(UPDATE, DELETE))
                        return;
                    setBusy(Messages.get().gen_saving());
                    /*
                     * create attachments for the newly uploaded files and get
                     * the managers for them
                     */
                    ams = AttachmentService.get().put();
                    if (managers == null)
                        managers = new HashMap<Integer, AttachmentManager>();

                    for (AttachmentManager am : ams)
                        managers.put(am.getAttachment().getId(), am);
                    previousManager = null;
                    setState(DISPLAY);
                    if (isLoadedFromQuery) {
                        /*
                         * the tree is showing the attachments returned by a
                         * previous search, so they are cleared and only the
                         * attachment created from the uploaded file is shown
                         */
                        isLoadedFromQuery = false;
                        loadTree(ams, true, false, isLoadedFromQuery);
                    } else {
                        /*
                         * add the attachment created from uploaded file as the
                         * first one in the tree
                         */
                        loadTree(ams, false, false, isLoadedFromQuery);
                    }
                    tree.selectNodeAt(0);
                    nodeSelected(0);
                    setDone(Messages.get().gen_savingComplete());
                } catch (Exception e) {
                    Window.alert(e.getMessage());
                    logger.log(Level.SEVERE, e.getMessage(), e);
                    clearStatus();
                }
            }
        };

        addStateChangeHandler(new StateChangeEvent.Handler() {
            public void onStateChange(StateChangeEvent event) {
                fileDrop.setEnabled(isState(QUERY, DISPLAY));

                if (isState(UPDATE, DELETE) && !closeHandlerAdded && window != null) {
                    /*
                     * this handler is not added in initialize() because this
                     * screen can be shown in a modal window and in that case,
                     * the window is not available in initialize()
                     */
                    window.addBeforeClosedHandler(new BeforeCloseHandler<WindowInt>() {
                        public void onBeforeClosed(BeforeCloseEvent<WindowInt> event) {
                            if (isState(UPDATE, DELETE)) {
                                event.cancel();
                                setError(Messages.get().gen_mustCommitOrAbort());
                            } else if (dropIndicator != null) {
                                dropIndicator.removeFromParent();
                                dropIndicator = null;
                            }
                        }
                    });
                    closeHandlerAdded = true;
                }
            }
        });

        fileDrop.addDragEnterHandler(new DragEnterHandler() {
            @Override
            public void onDragEnter(DragEnterEvent event) {
                LayoutPanel panel;

                if (dropIndicator == null) {
                    dropIndicator = new DropIndicator();
                    panel = (LayoutPanel) screen.getWidget();
                    panel.add(dropIndicator);
                    panel.setWidgetTopBottom(dropIndicator, 0, Unit.PX, 0, Unit.PX);
                }
            }
        });

        fileDrop.addDragLeaveHandler(new DragLeaveHandler() {
            @Override
            public void onDragLeave(DragLeaveEvent event) {
                if (dropIndicator != null) {
                    dropIndicator.removeFromParent();
                    dropIndicator = null;
                }
            }
        });

        /*
         * here two separate models are created for the two dropdowns to make
         * sure that any items disabled or enabled in one don't affect the other
         */
        model1 = new ArrayList<Item<Integer>>();
        model2 = new ArrayList<Item<Integer>>();
        for (SectionDO s : SectionCache.getList()) {
            model1.add(new Item<Integer>(s.getId(), s.getName()));
            model2.add(new Item<Integer>(s.getId(), s.getName()));
        }

        querySection.setModel(model1);
        tableSection.setModel(model2);

        /*
         * call for locking and fetching attachments
         */
        if (fetchForUpdateCall == null) {
            fetchForUpdateCall = new AsyncCallbackUI<AttachmentManager>() {
                public void success(AttachmentManager result) {
                    Node node;

                    manager = result;
                    managers.put(manager.getAttachment().getId(), manager);
                    setState(UPDATE);

                    node = tree.getNodeAt(tree.getSelectedNode());
                    if (ATTACHMENT_ITEM_LEAF.equals(node.getType()))
                        node = node.getParent();
                    reloadAttachment(node);
                    tree.selectNodeAt(node);
                    tree.startEditing(tree.getSelectedNode(), 0);
                }

                public void failure(Throwable e) {
                    Window.alert(e.getMessage());
                    logger.log(Level.SEVERE, e.getMessage() != null ? e.getMessage() : "null", e);
                }

                public void finish() {
                    clearStatus();
                }
            };
        }

        /*
         * call for updating an attachment
         */
        if (updateCall == null) {
            updateCall = new AsyncCallbackUI<AttachmentManager>() {
                public void success(AttachmentManager result) {
                    Node node;

                    manager = result;
                    managers.put(manager.getAttachment().getId(), manager);
                    setState(DISPLAY);

                    node = tree.getNodeAt(tree.getSelectedNode());
                    if (ATTACHMENT_ITEM_LEAF.equals(node.getType()))
                        node = node.getParent();
                    reloadAttachment(node);
                    tree.selectNodeAt(node);
                    nodeSelected(tree.getSelectedNode());
                    clearStatus();
                }

                public void validationErrors(ValidationErrorsList e) {
                    showErrors(e);
                }

                public void failure(Throwable e) {
                    Window.alert("commitUpdate(): " + e.getMessage());
                    logger.log(Level.SEVERE, e.getMessage() != null ? e.getMessage() : "null", e);
                    clearStatus();
                }
            };
        }

        /*
         * call for unlocking an attachment
         */
        if (unlockCall == null) {
            unlockCall = new AsyncCallbackUI<AttachmentManager>() {
                public void success(AttachmentManager result) {
                    Node node;

                    manager = result;
                    managers.put(manager.getAttachment().getId(), manager);
                    setState(DISPLAY);

                    node = tree.getNodeAt(tree.getSelectedNode());
                    if (ATTACHMENT_ITEM_LEAF.equals(node.getType()))
                        node = node.getParent();
                    reloadAttachment(node);
                    tree.selectNodeAt(node);
                    nodeSelected(tree.getSelectedNode());
                    setDone(Messages.get().gen_updateAborted());
                }

                public void failure(Throwable e) {
                    Window.alert(e.getMessage());
                    logger.log(Level.SEVERE, e.getMessage() != null ? e.getMessage() : "null", e);
                    clearStatus();
                }
            };
        }

        /*
         * call for deleting attachments
         */
        if (deleteCall == null) {
            deleteCall = new AsyncCallbackUI<Void>() {
                public void success(Void result) {
                    setState(DISPLAY);
                    setDone(Messages.get().gen_deletingComplete());
                }

                public void validationErrors(ValidationErrorsList e) {
                    showErrors(e);
                }

                public void failure(Throwable e) {
                    Window.alert(e.getMessage());
                    logger.log(Level.SEVERE, e.getMessage() != null ? e.getMessage() : "null", e);
                    clearStatus();
                }
            };
        }
    }

    @UiHandler("searchButton")
    protected void search(ClickEvent event) {
        search();
    }

    /**
     * Puts the screen in update state and loads it with a locked attachment.
     */
    @UiHandler("updateButton")
    protected void update(ClickEvent event) {
        Integer id;
        Node node;

        /*
         * manager will be null if the user selected the node for loading the
         * next page or if no node is selected
         */
        if (manager == null) {
            Window.alert(Messages.get().attachment_selectAnAttachment());
            return;
        }

        /*
         * allow update only if all selected nodes belong to the same attachment
         */
        id = null;
        for (int i : tree.getSelectedNodes()) {
            node = tree.getNodeAt(i);
            if (ATTACHMENT_ITEM_LEAF.equals(node.getType()))
                node = node.getParent();
            if (id == null) {
                id = node.getData();
            } else if (!id.equals(node.getData())) {
                Window.alert(Messages.get().attachment_selectOneAttachment());
                return;
            }
        }

        setBusy(Messages.get().gen_lockForUpdate());
        AttachmentService.get().fetchForUpdate(manager.getAttachment().getId(), fetchForUpdateCall);
    }

    /**
     * Puts the screen in delete state and deletes the nodes for all selected
     * attachments
     */
    @UiHandler("deleteButton")
    protected void delete(ClickEvent event) {
        Integer id, selNodes[];
        String desc, name;
        AttachmentManager am;
        SystemUserPermission perm;
        Node node;
        ArrayList<Node> removeNodes;

        /*
         * manager will be null if the user selected the node for loading the
         * next page or if no node is selected
         */
        if (manager == null) {
            Window.alert(Messages.get().attachment_selectAnAttachment());
            return;
        }

        setState(DELETE);
        deleteManagers = new ArrayList<AttachmentManager>();
        removeNodes = new ArrayList<Node>();
        perm = UserCache.getPermission();

        setBusy(Messages.get().gen_lockForDelete());
        /*
         * go through the selected nodes and make a list of managers and nodes
         * for the attachments that can be deleted; the list of nodes is used to
         * remove the nodes in a separate loop; they're not removed in this loop
         * because that'll mess up the indexes that this loop depends on
         */
        selNodes = tree.getSelectedNodes();
        Arrays.sort(selNodes);
        for (Integer i : selNodes) {
            node = tree.getNodeAt(i);
            if (ATTACHMENT_ITEM_LEAF.equals(node.getType()))
                node = node.getParent();

            id = node.getData();
            /*
             * the id will be null if the user selected the node that says
             * "Click for more records"
             */
            if (id == null)
                continue;

            am = managers.get(id);
            desc = am.getAttachment().getDescription();
            /*
             * try to lock each selected node's attachment and issue and refresh
             * the node with the fetched data
             */
            try {
                am = AttachmentService.get().fetchForUpdate(id);
                AttachmentService.get().fetchIssueForUpdate(id);
                manager = am;
                managers.put(id, am);
                reloadAttachment(node);
                /*
                 * the attachment will be deleted if it and its issue could be
                 * locked, it isn't attached and the user has permission to
                 * delete it; if it has an issue, ask the user to confirm
                 * deletion
                 */
                desc = am.getAttachment().getDescription();
                if (am.item.count() > 0) {
                    Window.alert(Messages.get().attachment_cantDeleteAttachedException(desc));
                    unlockAndReloadAttachment(id, node);
                    continue;
                }

                name = SectionCache.getById(am.getAttachment().getSectionId()).getName();
                if (!perm.has(name, SectionFlags.CANCEL)) {
                    Window.alert(Messages.get().attachment_deletePermException(desc));
                    unlockAndReloadAttachment(id, node);
                    continue;
                }

                if (am.getIssue() != null) {
                    if (!Window
                            .confirm(Messages.get().attachment_attachmentHasIssue(desc, am.getIssue().getText()))) {
                        unlockAndReloadAttachment(id, node);
                        continue;
                    }
                }
                /*
                 * this attachment can be deleted
                 */
                deleteManagers.add(am);
                removeNodes.add(node);
            } catch (EntityLockedException e) {
                /*
                 * either the attachment or the issue couldn't be locked; unlock
                 * them both
                 */
                Window.alert(Messages.get().attachment_attachmentLockException(desc, e.getUserName(),
                        new Date(e.getExpires()).toString()));
                unlockAndReloadAttachment(id, node);
            } catch (Exception e) {
                Window.alert(e.getMessage());
                logger.log(Level.SEVERE, e.getMessage() != null ? e.getMessage() : "null", e);
            }
        }

        /*
         * remove the nodes whose attachments can be deleted
         */
        if (removeNodes.size() > 0)
            for (Node n : removeNodes)
                tree.removeNode(n);

        /*
         * don't leave any nodes selected because the user may think that they
         * will get deleted when "Commit" is clicked, but they won't be; that's
         * because their attachments either can't be deleted possibly because
         * they're attached, or the user chose not to delete them because they
         * have issues
         */
        tree.unselectAll();
        manager = null;
        clearStatus();
    }

    /**
     * Validates the data on the screen and based on the current state, and
     * calls the service method to commit the data on the screen, to the
     * database. Shows any errors/warnings encountered during the commit,
     * otherwise refreshes the tree with the committed data.
     */
    @UiHandler("commitButton")
    protected void commit(ClickEvent event) {
        finishEditing();

        if (validate().getStatus() == Validation.Status.ERRORS) {
            setError(Messages.get().gen_correctErrors());
            return;
        }

        if (isState(UPDATE)) {
            setBusy(Messages.get().gen_updating());
            AttachmentService.get().update(manager, updateCall);
        } else if (isState(DELETE)) {
            if (deleteManagers == null || deleteManagers.size() == 0) {
                Window.alert(Messages.get().attachment_noAttachmentToDelete());
                return;
            }
            setBusy(Messages.get().gen_deleting());
            AttachmentService.get().delete(deleteManagers, deleteCall);
        }
    }

    /**
     * Reverts any changes made to the data on the screen and disables editing
     * of the widgets. If the attachment was locked, calls the service method to
     * unlock it and loads the screen with that data.
     */
    @UiHandler("abortButton")
    protected void abort(ClickEvent event) {
        int i, index;
        Integer insId, currId;
        Node root, insNode;

        finishEditing();
        clearErrors();
        setBusy(Messages.get().gen_cancelChanges());

        if (isState(UPDATE)) {
            AttachmentService.get().unlock(manager.getAttachment().getId(), unlockCall);
        } else if (isState(DELETE)) {
            /*
             * if some attachment nodes were deleted, unlock their attachments
             * and issues and re-insert them in the tree
             */
            if (deleteManagers != null) {
                /*
                 * the attachments in the tree are sorted in descending order of
                 * their ids; re-insert a deleted node right before the first
                 * node whose attachment id is less than the deleted node's
                 * attachment id
                 */
                root = tree.getRoot();
                while (deleteManagers.size() > 0) {
                    insNode = new Node(6);
                    insId = deleteManagers.get(0).getAttachment().getId();
                    unlockAndReloadAttachment(insId, insNode);
                    index = -1;
                    /*
                     * find the first attachment whose id is less than this
                     * attachment's
                     */
                    for (i = 0; i < root.getChildCount(); i++) {
                        currId = root.getChildAt(i).getData();
                        if (currId == null || insId > currId) {
                            index = i;
                            break;
                        }
                    }
                    /*
                     * either there are no nodes in the tree or this
                     * attachment's id is greater than all other nodes' ids;
                     * insert it at the end
                     */
                    if (index < 0)
                        index = root.getChildCount() == 0 ? 0 : root.getChildCount() - 1;
                    root.add(insNode, index);
                    deleteManagers.remove(0);
                }
                tree.setRoot(root);
                deleteManagers = null;
            }
            setState(DISPLAY);
            setDone(Messages.get().gen_deleteAborted());
        }
    }

    /**
     * If there are any attachments selected in the tree, then passes them to
     * the screen that brought attachment screen up and closes the window
     */
    @UiHandler("attachButton")
    protected void attach(ClickEvent event) {
        Integer currId, prevId, selNodes[];
        Node node;
        ArrayList<AttachmentDO> attachments;

        selNodes = tree.getSelectedNodes();
        prevId = null;
        attachments = new ArrayList<AttachmentDO>();
        for (int i = 0; i < selNodes.length; i++) {
            node = tree.getNodeAt(selNodes[i]);
            if (CLICK_FOR_MORE_LEAF.equals(node.getType()))
                break;
            /*
             * if an attachment item's node is selected then get the id of the
             * attachment from its parent node
             */
            if (ATTACHMENT_ITEM_LEAF.equals(node.getType()))
                node = node.getParent();

            currId = (Integer) node.getData();
            if (!currId.equals(prevId)) {
                attachments.add(managers.get(currId).getAttachment());
                prevId = currId;
            }
        }

        attach(attachments);
        window.close();
    }

    public void setWindow(WindowInt window) {
        super.setWindow(window);
        previousManager = null;
        /*
         * this flag needs to be reset every time the screen's window changes,
         * which can happen when the screen is brought up in a modal window and
         * in that case the handler for BeforeCloseHandler will be needed to be
         * added to the new window
         */
        closeHandlerAdded = false;
    }

    /**
     * Opens the file linked to the attachment showing on the passed node. If
     * "name" is null or if it's different from the previous time this method
     * was called then the file is opened in a new window, otherwise it's opened
     * in the same window as before.
     */
    public void displayAttachment(Node node, String name) {
        if (!ATTACHMENT_LEAF.equals(node.getType()))
            return;

        try {
            /*
             * passing the same name to displayAttachment makes sure that the
             * files open in the same window
             */
            AttachmentUtil.displayAttachment((Integer) node.getData(), name, window);
        } catch (Exception e) {
            Window.alert(e.getMessage());
            logger.log(Level.SEVERE, e.getMessage(), e);
        }
    }

    /**
     * Creates and executes a query to fetch the attachments to be shown on the
     * screen
     */
    public void search() {
        /*
         * query is a class variable because this screen doesn't use a screen
         * navigator but it needs to keep track of the previously executed query
         * and not a query created from the screen's current data
         */
        query = new Query();
        query.setFields(getQueryFields());
        query.setRowsPerPage(ROWS_PER_PAGE);
        isNewQuery = true;
        isLoadedFromQuery = true;
        managers = null;

        executeQuery(query);
    }

    /**
     * Uses the passed query to fetch attachments and refreshes the screen with
     * the returned data. If "fetchUnattached" is true then only unattached
     * attachments matching the query are fetched otherwise all matching
     * attachments are fetched.
     */
    public void executeQuery(final Query query) {
        setBusy(Messages.get().gen_querying());

        if (queryCall == null) {
            queryCall = new AsyncCallbackUI<ArrayList<AttachmentManager>>() {
                public void success(ArrayList<AttachmentManager> result) {
                    int index;

                    /*
                     * this map is used to link a tree node with the manager
                     * containing the attachment or attachment item that it's
                     * showing
                     */
                    if (managers == null)
                        managers = new HashMap<Integer, AttachmentManager>();

                    for (AttachmentManager am : result)
                        managers.put(am.getAttachment().getId(), am);

                    setState(DISPLAY);
                    index = isNewQuery ? index = 0 : tree.getRowCount() - 1;
                    loadTree(result, isNewQuery, true, isLoadedFromQuery);
                    clearStatus();
                    tree.selectNodeAt(index);
                    nodeSelected(index);
                    searchSuccessful();
                }

                public void notFound() {
                    setState(QUERY);
                    loadTree(null, isNewQuery, true, isLoadedFromQuery);
                    description.setFocus(true);
                    setDone(Messages.get().gen_noRecordsFound());
                }

                public void lastPage() {
                    int page;

                    /*
                     * make sure that the page doesn't stay one more than the
                     * current one if there are no more pages in this direction
                     */
                    page = query.getPage();
                    if (page > 0)
                        query.setPage(page - 1);
                    setError(Messages.get().gen_noMoreRecordInDir());
                }

                public void failure(Throwable e) {
                    Window.alert("Error: Attachment call query failed; " + e.getMessage());
                    logger.log(Level.SEVERE, e.getMessage(), e);
                    setError(Messages.get().gen_queryFailed());
                }
            };
        }

        AttachmentService.get().fetchByQueryDescending(query.getFields(), query.getPage() * query.getRowsPerPage(),
                query.getRowsPerPage(), queryCall);
    }

    /**
     * Returns true if the screen was opened from another screen for adding new
     * attachments to the system and then attaching them to a record, like
     * sample. Otherwise returns false.
     */
    public boolean isAttach() {
        return false;
    }

    /**
     * Defines the action to be performed when attachments selected by a user
     * are to be attached to another record like sample
     */
    public void attach(ArrayList<AttachmentDO> attachments) {
    }

    /**
     * Defines the action to be performed after the search executed on the
     * screen has completed and attachments were found
     */
    public void searchSuccessful() {
    }

    /**
     * Performs specific actions based on node selected in the tree, specified
     * by the passed index. If an attachment or attachment item was selected
     * then resets the manager; otherwise tries to load the next page, because
     * "Click for Next.." is selected. Also, enables or disables widgets.
     */
    private void nodeSelected(int index) {
        Integer id;
        Node node;

        node = tree.getNodeAt(index);
        id = null;
        if (ATTACHMENT_LEAF.equals(node.getType()))
            id = node.getData();
        else if (ATTACHMENT_ITEM_LEAF.equals(node.getType()))
            id = node.getParent().getData();
        else if (isState(DISPLAY))
            loadNextPage();

        /*
         * id is null if the node "Click for more records" is selected; "Update"
         * button is enabled if only one node is selected; "Delete" button is
         * enabled even if multiple nodes are selected
         */
        if (!isState(UPDATE))
            updateButton.setEnabled(id != null && (isState(DISPLAY)));
        if (!isState(DELETE))
            deleteButton.setEnabled((id != null || tree.getSelectedNodes().length > 1) && (isState(DISPLAY)));
        /*
         * the "Attach" button shouldn't be enabled unless any attachment or
         * attachment item is selected; but once it is enabled it shouldn't be
         * disabled on selecting "Click for more..."; that's because the tree
         * supports multiple selection, and other nodes may be already selected
         */
        if (id != null)
            attachButton.setEnabled(isAttach() && (isState(DISPLAY)));
        manager = id != null ? managers.get(id) : null;
    }

    /**
     * Executes the previously run query to fetch the next page of results
     */
    private void loadNextPage() {
        int page;

        /*
         * query is a class variable because this screen doesn't use a screen
         * navigator but it needs to keep track of the previously executed query
         * and not a query created from the screen's current data
         */
        page = query.getPage();

        query.setPage(page + 1);
        isNewQuery = false;
        executeQuery(query);
    }

    /**
     * Loads the tree with the data in the passed managers. If "reloadTree" is
     * true then loads the tree from scratch, otherwise only adds new nodes; if
     * "addAfter" is true then adds the new nodes after the current nodes,
     * otherwise adds them at the beginning. If "isLoadedFromQuery" and
     * "reloadTree" are both true then adds the node for "Click For More...",
     * because the tree is getting loaded from the results of a fresh query.
     */
    private void loadTree(ArrayList<AttachmentManager> ams, boolean reloadTree, boolean addAfter,
            boolean isLoadedFromQuery) {
        int sel, index;
        Node root, node;

        sel = tree.getSelectedNode();
        root = tree.getRoot();
        if (reloadTree || root == null) {
            /*
             * if no records were found by the query, empty the tree if this was
             * a new search and not getting the next page
             */
            root = new Node();
            tree.setRoot(root);
            if (reloadTree && ams == null)
                return;
        }

        index = -1;

        if (addAfter)
            /*
             * find the index of the last attachment node before the node for
             * "Click For More..."
             */
            index = tree.getRoot().getChildCount() - 2;

        /*
         * create and add the nodes for attachments and attachment items to the
         * tree; if "reloadTree" is true then the nodes are added to the root
         * because the tree will be reloaded, otherwise they're added after the
         * current nodes
         */
        if (ams != null) {
            for (AttachmentManager am : ams) {
                node = new Node(6);
                loadAttachment(node, am);
                if (reloadTree)
                    root.add(node);
                else
                    tree.addNodeAt(++index, node);
            }
        }

        if (reloadTree) {
            if (isLoadedFromQuery) {
                /*
                 * add the node for "Click for more...", because the tree is
                 * getting reloaded and is showing the results of a query
                 */
                node = new Node(1);
                node.setCell(0, Messages.get().gen_clickForMore());
                node.setType(CLICK_FOR_MORE_LEAF);
                root.add(node);
            }
            tree.setRoot(root);
        }

        /*
         * this needs to be done because tree.addNodeAt() gets rid of the
         * selection
         */
        if (sel > -1)
            tree.selectNodeAt(sel);
    }

    /**
     * Creates a subtree loaded from the passed manager. Makes passed node the
     * attachment node and also the root of the subtree.
     */
    private void loadAttachment(Node anode, AttachmentManager am) {
        int i;
        AttachmentDO data;
        AttachmentIssueViewDO issue;
        Node inode;

        data = am.getAttachment();
        /*
         * the node for the attachment
         */
        issue = am.getIssue();
        anode.setCell(0, data.getDescription());
        anode.setCell(1, data.getCreatedDate());
        anode.setCell(2, data.getSectionId());
        anode.setCell(3, issue != null ? issue.getText() : null);
        anode.setCell(4, issue != null ? issue.getSystemUserLoginName() : null);
        anode.setCell(5, data.getId());
        anode.setData(data.getId());
        anode.setType(ATTACHMENT_LEAF);
        /*
         * the nodes for the attachment items; these nodes are of type
         * AttachmentNode and not Node because otherwise they won't have the
         * blank icon and so the alignment of the text won't be like the nodes
         * at the top level
         */
        for (i = 0; i < am.item.count(); i++) {
            inode = new Node(1);
            inode.setCell(0, am.item.get(i).getReferenceDescription());
            inode.setType(ATTACHMENT_ITEM_LEAF);
            anode.add(inode);
        }
    }

    /**
     * Reloads the subtree rooted at the passed node from the latest data in the
     * manager for the currently selected attachment
     */
    private void reloadAttachment(Node node) {
        boolean isOpen;

        isOpen = node.isOpen();
        /*
         * this prevents any problems with trying to show the errors that were
         * added to the previous nodes and not to the latest ones added below
         */
        tree.clearExceptions();

        tree.close(node);
        node.removeAllChildren();
        loadAttachment(node, manager);

        /*
         * open the node in the new subtree if they were open in the old one
         */
        if (isOpen)
            tree.open(node);

        tree.refreshNode(node);
    }

    /**
     * Unlocks the attachment with the passed id and its issue; loads the passed
     * node with the latest data for the attachment from the database
     */
    private void unlockAndReloadAttachment(Integer attachmentId, Node node) {
        AttachmentManager am;

        try {
            am = AttachmentService.get().unlock(attachmentId);
            AttachmentService.get().unlockIssue(attachmentId);
            managers.put(attachmentId, am);
            node.removeAllChildren();
            loadAttachment(node, am);
        } catch (Exception e) {
            Window.alert(e.getMessage());
            logger.log(Level.SEVERE, e.getMessage() != null ? e.getMessage() : "null", e);
        }
    }
}