n3phele.client.view.RepoContentView.java Source code

Java tutorial

Introduction

Here is the source code for n3phele.client.view.RepoContentView.java

Source

/**
 * @author Nigel Cook
 *
 * (C) Copyright 2010-2012. Nigel Cook. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 * 
 * Licensed under the terms described in LICENSE file that accompanied this code, (the "License"); you may not use this file
 * except in compliance with the License. 
 * 
 *  Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 
 *  an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 
 *  specific language governing permissions and limitations under the License.
 */
package n3phele.client.view;

import java.util.ArrayList;
import java.util.List;

import com.google.gwt.cell.client.AbstractCell;
import com.google.gwt.cell.client.AbstractSafeHtmlCell;
import com.google.gwt.cell.client.FieldUpdater;
import com.google.gwt.cell.client.ValueUpdater;
import com.google.gwt.cell.client.ActionCell.Delegate;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.logical.shared.CloseEvent;
import com.google.gwt.event.logical.shared.CloseHandler;
import com.google.gwt.safehtml.shared.SafeHtml;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import com.google.gwt.safehtml.shared.SafeHtmlUtils;
import com.google.gwt.text.shared.SafeHtmlRenderer;
import com.google.gwt.text.shared.SimpleSafeHtmlRenderer;
import com.google.gwt.user.cellview.client.CellTable;
import com.google.gwt.user.cellview.client.Column;
import com.google.gwt.user.cellview.client.HasKeyboardSelectionPolicy.KeyboardSelectionPolicy;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HasHorizontalAlignment;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.MenuBar;
import com.google.gwt.user.client.ui.PopupPanel;
import com.google.gwt.user.client.ui.UIObject;
import com.google.gwt.user.client.ui.Widget;

import n3phele.client.N3phele;
import n3phele.client.model.FileNode;
import n3phele.client.model.Repository;
import n3phele.client.presenter.RepoContentActivity;
import n3phele.client.presenter.RepoPlace;
import n3phele.client.presenter.helpers.StyledTextCellRenderer;
import n3phele.client.widgets.ActionDialogBox;
import n3phele.client.widgets.FileDetailsPanel;
import n3phele.client.widgets.MenuItem;
import n3phele.client.widgets.NewFolderPanel;
import n3phele.client.widgets.SectionPanel;
import n3phele.client.widgets.UploadPanel;
import n3phele.client.widgets.WorkspaceVerticalPanel;

public class RepoContentView extends WorkspaceVerticalPanel {

    private static final int ROWLENGTH = 2;
    private HTML repositoryName;
    private String repositoryNameText = "";
    private HTML repositoryDescription;
    private String repositoryDescriptionText = "";
    private SectionPanel datasets;
    private FlowPanel crumbs;
    private CellTable<List<FileNode>> grid;
    private List<FileNode> crumbElements;
    private Repository repository;
    private RepoContentActivity presenter = null;
    private PopupPanel uploadPopup;
    private PopupPanel newFolderPopup;
    private String destination;
    private UploadPanel uploadPanel;
    private PopupPanel fileDetailsPopup;
    private List<List<FileNode>> rowList;

    /**
     * @param content
     * @param extras
     */
    public RepoContentView() {
        super(new MenuItem(N3phele.n3pheleResource.repositoryIcon(), "File Repository", null),
                new MenuItem(N3phele.n3pheleResource.repositoryIcon(), "edit", "repo"));

        HorizontalPanel title = new HorizontalPanel();
        title.setWidth("95%");
        repositoryName = new HTML(repositoryNameText);
        title.add(repositoryName);
        repositoryName.addStyleName(N3phele.n3pheleResource.css().commandDetailHeader());
        repositoryName.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_LEFT);
        repositoryDescription = new HTML(repositoryDescriptionText);
        repositoryDescription.addStyleName(N3phele.n3pheleResource.css().commandDetailHeader());
        title.add(repositoryDescription);
        this.add(title);

        /*
         * Setup dataset section of the view
         */

        datasets = new SectionPanel("Contents");

        Button addDataSet = new Button("<u>import</u>", new ClickHandler() {

            @Override
            public void onClick(ClickEvent event) {
                createUploadPopup();
            }
        });
        this.datasets.addButton(addDataSet);
        Button newFolder = new Button("<u>new folder</u>", new ClickHandler() {

            @Override
            public void onClick(ClickEvent event) {
                createNewFolderPopup(null);
            }
        });
        this.datasets.addButton(newFolder);

        this.datasets.add(createCrumbsPanel());
        this.datasets.add(createDatasetTable());
        this.add(datasets);
    }

    public void setPresenter(RepoContentActivity presenter) {
        this.presenter = presenter;
    }

    /**
     * @param repo
     */
    public void setData(Repository repository) {
        this.repository = repository;
        if (repository != null) {
            this.repositoryName.setText(this.repository.getName());
            this.repositoryDescription.setText(this.repository.getDescription());
            this.headerExtras.get(0).setTargetHistoryToken(RepoPlace.getPlaceToken(repository.getUri()));
        } else {
            this.show(nullList, null);
            this.grid.setRowCount(0, true);
            this.grid.setEmptyTableWidget(this.grid.getLoadingIndicator());
            this.grid.setVisibleRangeAndClearData(this.grid.getVisibleRange(), true);

        }
    }

    final static List<FileNode> nullList = new ArrayList<FileNode>(0);
    private static Widget emptyTable = null;

    /*
     * View Object creation
     */

    protected FlowPanel createCrumbsPanel() {
        crumbs = new FlowPanel();
        crumbs.setWidth("100%");
        return crumbs;

    }

    protected Widget createDatasetTable() {
        grid = new CellTable<List<FileNode>>();
        grid.setWidth("100%");
        grid.setTableLayoutFixed(true);

        for (int i = 0; i < ROWLENGTH; i++) {

            Column<List<FileNode>, FileNode> icon = new Column<List<FileNode>, FileNode>(new FileNodeIconCell(i)) {
                @Override
                public FileNode getValue(List<FileNode> object) {
                    int index = ((FileNodeIconCell) this.getCell()).getIndex();
                    if (index < object.size()) {
                        FileNode value = object.get(index);
                        return value;
                    } else {
                        return null;
                    }
                }
            };
            icon.setFieldUpdater(new FieldUpdater<List<FileNode>, FileNode>() {

                @Override
                public void update(int index, List<FileNode> object, FileNode value) {
                    if (value != null) {
                        String type = value.getMime();
                        PopupPanel popup;
                        if (!type.endsWith("Folder") && !type.endsWith("Placeholder")) {
                            GWT.log("got filename on row " + index + " " + value.toFormattedString());
                            popup = createFileDetailsPopup(value);
                        } else {
                            GWT.log("got folder on row " + index + " " + value.toFormattedString());
                            popup = createFileDetailsPopup(value);
                        }
                        if (popup != null) {
                            int column = 0;
                            for (int i = 0; i < ROWLENGTH && i < object.size(); i++) {
                                FileNode n = object.get(i);
                                if (n.getName() != null && n.getName().equals(value.getName())) {
                                    column = i;
                                    GWT.log("Found at " + column);
                                    break;
                                }
                            }

                            popup.showRelativeTo(new ElementWrapper(grid.getRowElement(index).getChild(column * 2)
                                    .getFirstChild().getParentElement().getFirstChildElement()));
                            popup.addAutoHidePartner(grid.getRowElement(index).getChild(column * 2).getFirstChild()
                                    .getParentElement());
                        }
                    }
                }
            });

            Column<List<FileNode>, FileNode> text = new Column<List<FileNode>, FileNode>(new FileNodeTextCell(i)) {
                @Override
                public FileNode getValue(List<FileNode> object) {
                    int index = ((FileNodeTextCell) this.getCell()).getIndex();
                    if (index < object.size()) {
                        FileNode value = object.get(index);
                        return value;
                    } else {
                        return null;
                    }
                }
            };
            text.setFieldUpdater(new FieldUpdater<List<FileNode>, FileNode>() {

                @Override
                public void update(int index, List<FileNode> object, FileNode value) {
                    if (value != null) {
                        if (isFile(value)) {
                            GWT.log("got filename on row " + index + " " + value.toFormattedString());
                        } else {
                            GWT.log("got folder on row " + index + " " + value.toFormattedString());
                            RepoContentView.this.presenter.selectFolder(value);
                        }
                    }
                }
            });

            grid.addColumn(icon);
            grid.setColumnWidth(icon, 50, Unit.PX);
            grid.addColumn(text);
            text.setCellStyleNames(N3phele.n3pheleResource.css().repoTextCell());
            grid.setColumnWidth(text, (100.0 / ROWLENGTH), Unit.PCT);

        }
        grid.setKeyboardSelectionPolicy(KeyboardSelectionPolicy.DISABLED);
        emptyTable = grid.getEmptyTableWidget();
        return grid;
    }

    private class ElementWrapper extends UIObject {
        public ElementWrapper(Element e) {
            setElement(e);
        }
    }

    public MenuBar getActionsPopup(final FileNode node, final PopupPanel popup) {
        MenuBar b = new MenuBar(true);
        b.setStyleName(N3phele.n3pheleResource.css().repoContentMenuBar(), true);
        if (node.getMime().endsWith("Folder")) {
            // Folder menu
            b.addItem(new com.google.gwt.user.client.ui.MenuItem("make public", new Command() {

                @Override
                public void execute() {
                    popup.hide();
                    presenter.makePublic(node, true);

                }
            }));
            b.addItem(new com.google.gwt.user.client.ui.MenuItem("make private", new Command() {

                @Override
                public void execute() {
                    popup.hide();
                    presenter.makePublic(node, false);

                }
            }));
            b.addSeparator();
            b.addItem(new com.google.gwt.user.client.ui.MenuItem("delete", new Command() {

                @Override
                public void execute() {
                    createDeleteFolderPopup(node);
                    popup.hide();
                }
            }));

        } else {
            // Regular file menu

            b.addItem(new com.google.gwt.user.client.ui.MenuItem("origin", new Command() {

                @Override
                public void execute() {
                    // createOriginPopup(node);
                    popup.hide();
                }
            }));

            b.addSeparator();

            b.addItem(new com.google.gwt.user.client.ui.MenuItem("delete", new Command() {

                @Override
                public void execute() {
                    createDeletePopup(node);
                    popup.hide();
                }
            }));

        }

        return b;
    }

    public UploadPanel getUploadPanel() {
        if (this.uploadPopup != null) {
            return this.uploadPanel;
        }
        return null;
    }

    private void createUploadPopup() {
        if (this.uploadPopup != null) {
            if (this.uploadPopup.isShowing()) {
                this.uploadPopup.hide();
                this.uploadPopup = null;
            }
        }
        this.uploadPopup = new PopupPanel(true);
        this.uploadPopup.addCloseHandler(new CloseHandler<PopupPanel>() {

            @Override
            public void onClose(CloseEvent<PopupPanel> event) {
                if (crumbElements != null && crumbElements.size() > 0) {
                    FileNode lastNode = crumbElements.get(crumbElements.size() - 1);
                    if (!uploadPanel.isCancelled()) {
                        presenter.clearPlaceholders();
                    }
                    presenter.selectFolder(lastNode);
                }
            }
        });
        this.uploadPopup.setStyleName(N3phele.n3pheleResource.css().repoContentMenuPanel(), true);
        destination = "";
        if (this.crumbElements != null && this.crumbElements.size() > 1) {
            FileNode lastNode = crumbElements.get(this.crumbElements.size() - 1);
            String path = lastNode.getPath();
            if (path == null)
                path = "";
            destination = path + lastNode.getName() + "/";
        }
        uploadPanel = UploadPanel.getInstance(uploadPopup, destination, presenter);
        uploadPopup.add(uploadPanel);
        uploadPopup.center();
    }

    protected void createDeletePopup(FileNode node) {
        ActionDialogBox<FileNode> deletePopup = new ActionDialogBox<FileNode>("File Delete Confirmation", "No",
                "Yes", new Delegate<FileNode>() {

                    @Override
                    public void execute(FileNode object) {
                        presenter.deleteFile(object);

                    }
                });
        deletePopup.setGlassEnabled(true);
        deletePopup.setAnimationEnabled(true);

        deletePopup.setValue(
                "Remove file \"" + node.getName() + "\" from repository " + node.getRepositoryName() + "?<p>",
                node);
        deletePopup.center();
    }

    protected void createDeleteFolderPopup(FileNode node) {
        ActionDialogBox<FileNode> deleteFolderPopup = new ActionDialogBox<FileNode>("Folder Delete Confirmation",
                "No", "Yes", new Delegate<FileNode>() {

                    @Override
                    public void execute(FileNode object) {
                        presenter.deleteFolder(object);

                    }
                });
        deleteFolderPopup.setGlassEnabled(true);
        deleteFolderPopup.setAnimationEnabled(true);

        deleteFolderPopup.setValue("Remove folder \"" + node.getName() + "\" from repository "
                + node.getRepositoryName() + "?<p>"
                + "For large filesets, this operation may take up to 10 minutes to complete.<p> The folder will refresh when the operation completes",
                node);
        deleteFolderPopup.center();
    }

    private void createNewFolderPopup(FileNode node) {
        if (this.newFolderPopup != null) {
            if (this.newFolderPopup.isShowing()) {
                this.newFolderPopup.hide();
                this.newFolderPopup = null;
            }
        }
        this.newFolderPopup = new PopupPanel(true);

        this.newFolderPopup.setStyleName(N3phele.n3pheleResource.css().repoContentMenuPanel(), true);
        final NewFolderPanel newFilePanel = NewFolderPanel.getInstance(newFolderPopup, repository);
        newFolderPopup.add(newFilePanel);
        this.newFolderPopup.addCloseHandler(new CloseHandler<PopupPanel>() {

            @Override
            public void onClose(CloseEvent<PopupPanel> event) {
                String name = newFilePanel.getFolderName();
                if (name != null && (name = name.trim()).length() > 0) {
                    newFolder(name);
                }
            }
        });
        newFolderPopup.center();
    }

    private String getCanonicalFilename(String filename) {
        String result = "";
        if (this.crumbElements != null && this.crumbElements.size() > 1) {
            FileNode tail = this.crumbElements.get(this.crumbElements.size() - 1);
            String path = tail.getPath();
            String name = tail.getName();
            if (path == null) {
                result = name + "/";
            } else {
                result = path + name + "/";
            }
        }

        return result + filename;
    }

    /**
     * @param name
     */
    protected void newFolder(String name) {
        GWT.log("new folder " + name);
        if (name.endsWith("/"))
            name = name.substring(0, name.length() - 1);
        String path = getCanonicalFilename("");
        FileNode folder = FileNode.newInstance(name, path, "Placeholder", this.crumbElements.get(0).getRepository(),
                this.crumbElements.get(0).getRepositoryName());
        GWT.log(folder.toFormattedString());
        if (this.presenter != null) {
            this.presenter.addPlaceholder(folder);
        }
    }

    public void show(List<FileNode> crumbs, List<FileNode> files) {
        if (crumbs.size() > 0) {

            int delta = match(crumbElements, crumbs);
            GWT.log("delta = " + delta);
            if (trimCrumbs(delta))
                delta = 0;

            for (int i = delta; i < crumbs.size(); i++) {
                Button button;
                if (i == 0) {
                    button = new Button(getFilename(crumbs.get(i).getRepositoryName()));
                } else {
                    this.crumbs.add(new Image(N3phele.n3pheleResource.rightArrowHead()));
                    button = new Button(getFilename(crumbs.get(i).getName()));
                }

                button.setStyleName(N3phele.n3pheleResource.css().crumbsButton());
                button.addClickHandler(new CrumbsClickHandler(crumbs.get(i), this));
                this.crumbs.add(button);
            }
        } else {
            trimCrumbs(0);
        }
        crumbElements = crumbs;
        if (files != null) {
            rowList = new ArrayList<List<FileNode>>((files.size() + ROWLENGTH - 1) / ROWLENGTH);
            for (int i = 0; i < files.size(); i = i + ROWLENGTH)
                rowList.add(files.subList(i, files.size()));
            if (rowList.isEmpty() && (this.grid.getEmptyTableWidget() != emptyTable))
                this.grid.setEmptyTableWidget(emptyTable);
        } else {
            rowList = emptyRowList;
        }
        grid.setRowData(rowList);

    }

    private final static ArrayList<List<FileNode>> emptyRowList = new ArrayList<List<FileNode>>(0);

    public static class CrumbsClickHandler implements ClickHandler {

        private final FileNode path;
        private final RepoContentView scope;

        public CrumbsClickHandler(FileNode path, RepoContentView scope) {
            this.path = path;
            this.scope = scope;
        }

        /* (non-Javadoc)
         * @see com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt.event.dom.client.ClickEvent)
         */
        @Override
        public void onClick(ClickEvent event) {
            if (scope.presenter != null) {
                scope.presenter.selectFolder(path);
            }
            GWT.log("Crumbs for " + path.toFormattedString());
        }

    }

    private PopupPanel createFileDetailsPopup(FileNode node) {
        if (this.fileDetailsPopup != null) {
            FileDetailsPanel panel = (FileDetailsPanel) fileDetailsPopup.getWidget();
            if (this.fileDetailsPopup.isShowing()) {
                this.fileDetailsPopup.hide();
                if (panel.isShowing(node))
                    return null; // if user has clicked twice dismiss popup
            }
            panel.setFileNode(node);
            if (isFile(node)) {
                presenter.getOrigin(node, panel);
            }
        } else {
            this.fileDetailsPopup = new PopupPanel(true);

            this.fileDetailsPopup.setStyleName(N3phele.n3pheleResource.css().repoContentMenuPanel(), true);
            this.fileDetailsPopup.setGlassEnabled(false);
            this.fileDetailsPopup.setAnimationEnabled(true);
            final FileDetailsPanel fileDetailsPanel = FileDetailsPanel.getInstance(fileDetailsPopup, node,
                    new Delegate<FileNode>() { // delete

                        @Override
                        public void execute(FileNode object) {
                            if (isFile(object)) {
                                if (fileDetailsPopup.isShowing())
                                    fileDetailsPopup.hide();
                                RepoContentView.this.createDeletePopup(object);
                                GWT.log("Delete File " + object);
                            } else {
                                if (fileDetailsPopup.isShowing())
                                    fileDetailsPopup.hide();
                                RepoContentView.this.createDeleteFolderPopup(object);
                                GWT.log("Delete Folder " + object);
                            }
                        }
                    }, new Delegate<FileNode>() { // make public

                        @Override
                        public void execute(FileNode object) {
                            if (fileDetailsPopup.isShowing())
                                fileDetailsPopup.hide();
                            presenter.makePublic(object, true);
                            GWT.log("Make public " + object);
                        }
                    }, new Delegate<FileNode>() { // make Private

                        @Override
                        public void execute(FileNode object) {
                            if (fileDetailsPopup.isShowing())
                                fileDetailsPopup.hide();
                            presenter.makePublic(object, false);
                            GWT.log("Make private " + object);
                        }
                    });
            if (isFile(node)) {
                presenter.getOrigin(node, fileDetailsPanel);
            }
            fileDetailsPopup.add(fileDetailsPanel);
        }
        return this.fileDetailsPopup;
    }

    private static boolean isFile(FileNode node) {
        return node.getMime() != null
                && !(node.getMime().endsWith("Folder") || node.getMime().endsWith("Placeholder"));
    }

    /** Provides a click handler for cell rendering FileNode
     * @author Nigel Cook
     *
     * (C) Copyright 2010. All rights reserved.
     * 
     *
     */
    public static class FileNodeTextCell extends AbstractSafeHtmlCell<FileNode> {
        final private int index;

        public int getIndex() {
            return this.index;
        }

        /**
         * Constructs a TextCell that uses a {@link SimpleSafeHtmlRenderer} to render
         * its text.
         */
        public FileNodeTextCell(int index) {
            super(RepoItemNameSafeHtmlRenderer.getInstance(), "click", "keydown");
            this.index = index;
        }

        /**
         * Constructs a TextCell that uses the provided {@link SafeHtmlRenderer} to
         * render its text.
         * 
         * @param renderer a {@link SafeHtmlRenderer SafeHtmlRenderer<String>} instance
         */
        public FileNodeTextCell(SafeHtmlRenderer<FileNode> renderer, int index) {
            super(renderer, "click", "keydown");
            this.index = index;
        }

        @Override
        public void onBrowserEvent(Context context, Element parent, FileNode value, NativeEvent event,
                ValueUpdater<FileNode> valueUpdater) {
            super.onBrowserEvent(context, parent, value, event, valueUpdater);
            if ("click".equals(event.getType())) {
                onEnterKeyDown(context, parent, value, event, valueUpdater);
            }
        }

        @Override
        protected void onEnterKeyDown(Context context, Element parent, FileNode value, NativeEvent event,
                ValueUpdater<FileNode> valueUpdater) {
            if (valueUpdater != null) {
                valueUpdater.update(value);
            }
        }

        @Override
        public void render(Context context, SafeHtml value, SafeHtmlBuilder sb) {
            if (value != null) {
                sb.append(value);
            }
        }
    }

    public static class RepoItemNameSafeHtmlRenderer extends StyledTextCellRenderer<FileNode> {

        private static RepoItemNameSafeHtmlRenderer instance;

        public static RepoItemNameSafeHtmlRenderer getInstance() {
            if (instance == null) {
                instance = new RepoItemNameSafeHtmlRenderer(N3phele.n3pheleResource.css().repoContentIconText());
            }
            return instance;
        }

        public RepoItemNameSafeHtmlRenderer(String style) {
            super(style);
        }

        public String getValue(FileNode object) {
            if (object != null)
                return getFilename(object.getName());
            else
                return null;
        }

        @Override
        public String getTooltip(FileNode object) {
            return getFileNodeTooltip(object);
        }

        @Override
        public SafeHtml render(FileNode object) {

            String name = this.getValue(object);
            if (!isFile(object)) {
                return template.text(SafeHtmlUtils.fromString(name), getTooltip(object), style);
            }
            return template.text(SafeHtmlUtils.fromSafeConstant("<a href='" + redirectAddr(object)
                    + "' target='_blank'>" + SafeHtmlUtils.htmlEscape(name) + "</a>"), name, style);

        }

        @Override
        public void render(FileNode object, SafeHtmlBuilder appendable) {
            String name = this.getValue(object);
            if (!isFile(object)) {
                appendable.append(template.text(SafeHtmlUtils.fromString(name), getTooltip(object), style));
            } else {
                appendable
                        .append(template.text(
                                SafeHtmlUtils.fromSafeConstant("<a href='" + redirectAddr(object)
                                        + "' target='_blank'>" + SafeHtmlUtils.htmlEscape(name) + "</a>"),
                                name, style));
            }
        }

        private String redirectAddr(FileNode object) {
            String x = object.getRepository() + "/redirect" + "?name=" + object.getName();
            if (object.getPath() != null) {
                x += "&path=" + object.getPath();
            }
            return x;
        }
    }

    private static class FileNodeIconCell extends AbstractCell<FileNode> {

        public FileNodeIconCell(int index) {
            super("click", "keydown");
            this.index = index;
        }

        @Override
        public void onBrowserEvent(Context context, Element parent, FileNode value, NativeEvent event,
                ValueUpdater<FileNode> valueUpdater) {
            super.onBrowserEvent(context, parent, value, event, valueUpdater);
            if ("click".equals(event.getType())) {
                onEnterKeyDown(context, parent, value, event, valueUpdater);
            }
        }

        @Override
        protected void onEnterKeyDown(Context context, Element parent, FileNode value, NativeEvent event,
                ValueUpdater<FileNode> valueUpdater) {
            if (valueUpdater != null) {
                valueUpdater.update(value);
            }
        }

        @Override
        public void render(Context context, FileNode value, SafeHtmlBuilder sb) {
            if (value != null) {
                String icon;
                if (value.getMime().endsWith("Folder")) {
                    if (value.getMime().endsWith("PublicFolder")) {
                        icon = N3phele.n3pheleResource.publicFolder().getSafeUri().asString();
                    } else {
                        icon = N3phele.n3pheleResource.folderIcon().getSafeUri().asString();
                    }
                } else if (value.getMime().endsWith("Placeholder")) {
                    icon = N3phele.n3pheleResource.folderAddedIcon().getSafeUri().asString();
                } else {
                    icon = N3phele.n3pheleResource.fileIcon().getSafeUri().asString();
                }
                sb.append(SafeHtmlUtils.fromSafeConstant("<img src=\"" + icon + "\"/>"));
            }
        }

        final private int index;

        public int getIndex() {
            return this.index;
        }
    }

    public static String getFileNodeTooltip(FileNode value) {
        if (value.getMime().endsWith("Folder")) {
            return (value.getMime().endsWith("PublicFolder") ? "Public Folder " : "Folder ") + value.getName();
        } else if (value.getMime().endsWith("Placeholder")) {
            return "Placeholder Folder " + value.getName();
        } else {
            return value.getName();
        }
    }

    /** Generates the index at which a is dissimilar to b
     * @param a
     * @param b
     * @return
     */
    public int match(List<FileNode> a, List<FileNode> b) {
        int len = Math.min(a == null ? 0 : a.size(), b == null ? 0 : b.size());

        for (int i = 0; i < len; i++) {
            if (!(isNullOrEquals(a.get(i).getRepositoryName(), b.get(i).getRepositoryName())
                    && isNullOrEquals(a.get(i).getName(), b.get(i).getName())))
                return i;
        }
        return len;
    }

    private boolean isNullOrEquals(String a, String b) {
        if (a == null) {
            return b == null;
        }
        return a.equals(b);
    }

    public boolean trimCrumbs(int size) {
        if (crumbElements != null && crumbElements.size() > size) {
            crumbElements.clear();
            crumbs.clear();
            return true;
        }
        return false;
    }

    public static String getFilename(String pathName) {
        String result = pathName;
        if (pathName.endsWith("/"))
            result = pathName.substring(0, pathName.length() - 1);
        return result;
    }
}