com.haulmont.cuba.desktop.gui.components.DesktopTree.java Source code

Java tutorial

Introduction

Here is the source code for com.haulmont.cuba.desktop.gui.components.DesktopTree.java

Source

/*
 * Copyright (c) 2008-2016 Haulmont.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package com.haulmont.cuba.desktop.gui.components;

import com.haulmont.cuba.core.entity.Entity;
import com.haulmont.cuba.core.global.AppBeans;
import com.haulmont.cuba.core.global.UserSessionSource;
import com.haulmont.cuba.desktop.gui.data.TreeModelAdapter;
import com.haulmont.cuba.desktop.gui.icons.IconResolver;
import com.haulmont.cuba.gui.ComponentsHelper;
import com.haulmont.cuba.gui.components.Action;
import com.haulmont.cuba.gui.components.*;
import com.haulmont.cuba.gui.components.Window;
import com.haulmont.cuba.gui.data.CollectionDatasource;
import com.haulmont.cuba.gui.data.Datasource;
import com.haulmont.cuba.gui.data.HierarchicalDatasource;
import com.haulmont.cuba.gui.data.impl.CollectionDsActionsNotifier;
import com.haulmont.cuba.gui.data.impl.WeakCollectionChangeListener;
import net.miginfocom.swing.MigLayout;
import org.apache.commons.lang.StringUtils;

import javax.annotation.Nullable;
import javax.swing.*;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.*;
import java.util.List;

public class DesktopTree<E extends Entity> extends DesktopAbstractActionsHolderComponent<JTree>
        implements Tree<E>, LookupComponent.LookupSelectionChangeNotifier {

    protected String hierarchyProperty;
    protected HierarchicalDatasource<Entity<Object>, Object> datasource;
    protected JScrollPane treeView;
    protected CaptionMode captionMode = CaptionMode.ITEM;
    protected String captionProperty;
    protected TreeModelAdapter model;

    protected ButtonsPanel buttonsPanel;
    protected MigLayout layout;
    protected JPanel panel;
    protected JPanel topPanel;

    protected Action doubleClickAction;
    protected MouseAdapter itemClickListener;
    protected Action enterPressAction;
    protected boolean editable = true;

    protected CollectionDatasource.CollectionChangeListener collectionChangeListener;

    protected CollectionDsActionsNotifier collectionDsActionsNotifier;

    protected List<LookupSelectionChangeListener> lookupSelectionChangeListeners = new ArrayList<>();

    public DesktopTree() {
        layout = new MigLayout("flowy, fill, insets 0", "", "[min!][fill]");
        panel = new JPanel(layout);

        topPanel = new JPanel(new BorderLayout());
        topPanel.setVisible(false);
        panel.add(topPanel, "growx");

        impl = new JTree();
        treeView = new JScrollPane(impl);
        panel.add(treeView, "grow");

        impl.setRootVisible(false);
        impl.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
        impl.setExpandsSelectedPaths(true);

        impl.addMouseListener(new MouseAdapter() {

            @Override
            public void mousePressed(MouseEvent e) {
                showPopup(e);
            }

            @Override
            public void mouseReleased(MouseEvent e) {
                showPopup(e);
            }

            private void showPopup(MouseEvent e) {
                if (e.isPopupTrigger()) {
                    // select row
                    Point p = e.getPoint();
                    TreePath treePath = impl.getPathForLocation(p.x, p.y);
                    if (treePath != null) {
                        TreeSelectionModel model = impl.getSelectionModel();
                        model.setSelectionPath(treePath);
                    }
                    // show popup menu
                    createPopupMenu().show(e.getComponent(), e.getX(), e.getY());
                }
            }
        });

        impl.addKeyListener(new KeyAdapter() {
            protected static final int ENTER_CODE = 10;

            @Override
            public void keyPressed(KeyEvent e) {
                if (ENTER_CODE == e.getKeyCode() && e.getComponent() == DesktopTree.this.getComponent()) {
                    if (enterPressAction != null) {
                        enterPressAction.actionPerform(DesktopTree.this);
                    } else {
                        handleClickAction();
                    }
                }
            }
        });
    }

    protected void handleClickAction() {
        Action action = getItemClickAction();
        if (action == null) {
            action = getEnterPressAction();
            if (action == null) {
                action = getAction("edit");
                if (action == null) {
                    action = getAction("view");
                }
            }
        }

        if (action != null && action.isEnabled()) {
            Window window = ComponentsHelper.getWindowImplementation(DesktopTree.this);
            if (window instanceof Window.Wrapper) {
                window = ((Window.Wrapper) window).getWrappedWindow();
            }

            if (!(window instanceof Window.Lookup)) {
                action.actionPerform(DesktopTree.this);
            } else {
                Window.Lookup lookup = (Window.Lookup) window;

                com.haulmont.cuba.gui.components.Component lookupComponent = lookup.getLookupComponent();
                if (lookupComponent != this)
                    action.actionPerform(DesktopTree.this);
                else if (action.getId().equals(WindowDelegate.LOOKUP_ITEM_CLICK_ACTION_ID)) {
                    action.actionPerform(DesktopTree.this);
                }
            }
        }
    }

    @Override
    public void setLookupSelectHandler(Runnable selectHandler) {
        impl.addMouseListener(new MouseAdapter() {
            @Override
            public void mousePressed(MouseEvent e) {
                if (e.getClickCount() == 2 && e.getButton() == MouseEvent.BUTTON1) {
                    int rowForLocation = impl.getRowForLocation(e.getX(), e.getY());
                    TreePath pathForLocation = impl.getPathForRow(rowForLocation);
                    if (pathForLocation != null) {
                        CollectionDatasource treeCds = getDatasource();
                        if (treeCds != null) {
                            TreeModelAdapter.Node treeItem = (TreeModelAdapter.Node) pathForLocation
                                    .getLastPathComponent();
                            if (treeItem != null) {
                                treeCds.setItem(treeItem.getEntity());
                                selectHandler.run();
                            }
                        }
                    }
                }
            }
        });
    }

    @Override
    public Collection getLookupSelectedItems() {
        return getSelected();
    }

    @Override
    public JComponent getComposition() {
        return panel;
    }

    @Override
    protected void updateEnabled() {
        super.updateEnabled();

        impl.setEnabled(isEnabledWithParent());
    }

    @Override
    public void expandTree() {
        if (model == null) {
            return;
        }

        if (!model.isLeaf(model.getRoot())) {
            recursiveExpand(model.getRoot());
        }
    }

    @Override
    public ButtonsPanel getButtonsPanel() {
        return buttonsPanel;
    }

    @Override
    public void setButtonsPanel(ButtonsPanel panel) {
        if (buttonsPanel != null && topPanel != null) {
            topPanel.remove(DesktopComponentsHelper.unwrap(buttonsPanel));
            buttonsPanel.setParent(null);
        }
        buttonsPanel = panel;
        if (panel != null) {
            if (panel.getParent() != null && panel.getParent() != this) {
                throw new IllegalStateException("Component already has parent");
            }

            topPanel.add(DesktopComponentsHelper.unwrap(panel), BorderLayout.LINE_START);
            topPanel.setVisible(true);
            panel.setParent(this);
        }
    }

    private void recursiveExpand(Object node) {
        impl.expandPath(model.getTreePath(node));
        for (int i = 0; i < model.getChildCount(node); i++) {
            Object child = model.getChild(node, i);
            if (!model.isLeaf(child)) {
                impl.expandPath(model.getTreePath(child));
                recursiveExpand(child);
            }
        }
    }

    @Override
    public void expand(Object itemId) {
        if (datasource == null) {
            return;
        }
        Entity<Object> item = datasource.getItem(itemId);
        if (item == null) {
            return;
        }

        impl.expandPath(model.getTreePath(item));
    }

    @Override
    public void collapseTree() {
        if (model == null) {
            return;
        }

        impl.collapsePath(new TreePath(model.getRoot()));
    }

    @Override
    public void collapse(Object itemId) {
        if (datasource == null) {
            return;
        }
        Entity<Object> item = datasource.getItem(itemId);
        if (item == null) {
            return;
        }

        impl.collapsePath(model.getTreePath(item));
    }

    @Override
    public void expandUpTo(int level) {
        if (getDatasource() == null) {
            return;
        }

        HierarchicalDatasource ds = getDatasource();
        java.util.List<Object> currentLevelItemIds = new ArrayList<>(ds.getRootItemIds());
        int i = 0;
        while (i < level && !currentLevelItemIds.isEmpty()) {
            for (Object itemId : new ArrayList<>(currentLevelItemIds)) {
                Entity<Object> item = datasource.getItem(itemId);
                impl.expandPath(model.getTreePath(item));

                currentLevelItemIds.remove(itemId);
                currentLevelItemIds.addAll(ds.getChildren(itemId));
            }
            i++;
        }
    }

    @Override
    public boolean isExpanded(Object itemId) {
        if (datasource == null) {
            return false;
        }
        Entity<Object> item = datasource.getItem(itemId);
        if (item == null) {
            return false;
        }

        return impl.isExpanded(model.getTreePath(item));
    }

    @Override
    public CaptionMode getCaptionMode() {
        return captionMode;
    }

    @Override
    public void setCaptionMode(CaptionMode captionMode) {
        if (this.captionMode != captionMode) {
            this.captionMode = captionMode;
            if (model != null) {
                model.setCaptionMode(captionMode);
            }
        }
    }

    @Override
    public String getCaptionProperty() {
        return captionProperty;
    }

    @Override
    public void setCaptionProperty(String captionProperty) {
        if (!Objects.equals(this.captionProperty, captionProperty)) {
            this.captionProperty = captionProperty;
            if (model != null) {
                model.setCaptionProperty(captionProperty);

                if (captionProperty != null) {
                    setCaptionMode(CaptionMode.PROPERTY);
                } else {
                    setCaptionMode(CaptionMode.ITEM);
                }
            }
        }
    }

    @Override
    public String getHierarchyProperty() {
        return hierarchyProperty;
    }

    @Override
    public void setDatasource(HierarchicalDatasource datasource) {
        this.datasource = datasource;
        hierarchyProperty = datasource.getHierarchyPropertyName();

        model = new TreeModelAdapter(datasource, captionMode, captionProperty, true);
        impl.setModel(model);

        impl.addTreeSelectionListener(new SelectionListener());

        UserSessionSource uss = AppBeans.get(UserSessionSource.NAME);
        if (uss.getUserSession().isSpecificPermitted(ShowInfoAction.ACTION_PERMISSION)) {
            ShowInfoAction action = (ShowInfoAction) getAction(ShowInfoAction.ACTION_ID);
            if (action == null) {
                action = new ShowInfoAction();
                addAction(action);
            }
            action.setDatasource(datasource);
        }

        collectionChangeListener = e -> {
            // #PL-2035, reload selection from ds
            Set<E> selectedItems = getSelected();
            if (selectedItems == null) {
                selectedItems = Collections.emptySet();
            }

            Set<E> newSelection = new HashSet<>();
            for (E entity : selectedItems) {
                if (e.getDs().containsItem(entity.getId())) {
                    newSelection.add(entity);
                }
            }

            if (e.getDs().getState() == Datasource.State.VALID && e.getDs().getItem() != null) {
                //noinspection unchecked
                newSelection.add((E) e.getDs().getItem());
            }

            if (newSelection.isEmpty()) {
                setSelected((Entity) null);
            } else {
                setSelected(newSelection);
            }
        };
        //noinspection unchecked
        datasource.addCollectionChangeListener(
                new WeakCollectionChangeListener(datasource, collectionChangeListener));

        collectionDsActionsNotifier = new CollectionDsActionsNotifier(this);
        collectionDsActionsNotifier.bind(datasource);

        for (Action action : getActions()) {
            action.refreshState();
        }

        assignAutoDebugId();
    }

    @Override
    public void setItemClickAction(Action action) {
        if (this.doubleClickAction != action) {
            if (action != null) {
                if (itemClickListener == null) {
                    itemClickListener = new MouseAdapter() {
                        @Override
                        public void mouseClicked(MouseEvent e) {
                            if (isEditable()) {
                                if (e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() == 2
                                        && doubleClickAction != null) {

                                    int rowForLocation = impl.getRowForLocation(e.getX(), e.getY());
                                    TreePath pathForLocation = impl.getPathForRow(rowForLocation);

                                    if (pathForLocation != null) {
                                        impl.setSelectionPath(pathForLocation);

                                        doubleClickAction.actionPerform(DesktopTree.this);
                                    }
                                }
                            }
                        }
                    };
                    impl.addMouseListener(itemClickListener);
                    impl.setToggleClickCount(0);
                }
            } else {
                impl.removeMouseListener(itemClickListener);
                impl.setToggleClickCount(2);
                itemClickListener = null;
            }
            this.doubleClickAction = action;
        }
    }

    @Override
    public Action getItemClickAction() {
        return doubleClickAction;
    }

    @Override
    protected String getAlternativeDebugId() {
        if (id != null) {
            return id;
        }
        if (datasource != null && StringUtils.isNotEmpty(datasource.getId())) {
            return getClass().getSimpleName() + "_" + datasource.getId();
        }

        return getClass().getSimpleName();
    }

    @Override
    public boolean isMultiSelect() {
        return impl.getSelectionModel().getSelectionMode() != TreeSelectionModel.SINGLE_TREE_SELECTION;
    }

    @Override
    public void setMultiSelect(boolean multiselect) {
        int mode = multiselect ? TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION
                : TreeSelectionModel.SINGLE_TREE_SELECTION;
        impl.getSelectionModel().setSelectionMode(mode);
    }

    @Override
    public E getSingleSelected() {
        Set<E> selected = getSelected();
        return selected.isEmpty() ? null : selected.iterator().next();
    }

    @SuppressWarnings("unchecked")
    @Override
    public Set<E> getSelected() {
        Set<E> selected = new HashSet<>();
        TreePath[] selectionPaths = impl.getSelectionPaths();
        if (selectionPaths != null) {
            for (TreePath selectionPath : selectionPaths) {
                Entity entity = model.getEntity(selectionPath.getLastPathComponent());
                if (entity != null) {
                    selected.add((E) entity);
                }
            }
        }
        return selected;
    }

    @Override
    public void setSelected(@Nullable Entity item) {
        if (item != null) {
            TreePath path = model.getTreePath(item);
            impl.setSelectionPath(path);
        } else {
            impl.setSelectionPath(null);
        }
    }

    @Override
    public void setSelected(Collection<E> items) {
        TreePath[] paths = new TreePath[items.size()];
        int i = 0;
        for (Entity item : items) {
            paths[i] = model.getTreePath(item);
        }
        impl.setSelectionPaths(paths);
    }

    @Override
    public HierarchicalDatasource getDatasource() {
        return datasource;
    }

    @Override
    public void refresh() {
        datasource.refresh();
    }

    @Override
    public boolean isEditable() {
        return editable;
    }

    @Override
    public void setEditable(boolean editable) {
        this.editable = editable;
    }

    @Override
    public String getCaption() {
        return caption;
    }

    @Override
    public void setCaption(String caption) {
        this.caption = caption;
    }

    @Override
    public String getDescription() {
        return impl.getToolTipText();
    }

    @Override
    public void setDescription(String description) {
        impl.setToolTipText(description);
    }

    @Override
    protected void attachAction(Action action) {
        if (action instanceof Action.HasTarget) {
            ((Action.HasTarget) action).setTarget(this);
        }

        super.attachAction(action);
    }

    @Override
    public void addLookupValueChangeListener(LookupSelectionChangeListener listener) {
        if (!lookupSelectionChangeListeners.contains(listener)) {
            lookupSelectionChangeListeners.add(listener);
        }
    }

    @Override
    public void removeLookupValueChangeListener(LookupSelectionChangeListener listener) {
        lookupSelectionChangeListeners.remove(listener);
    }

    protected class SelectionListener implements TreeSelectionListener {

        @SuppressWarnings("unchecked")
        @Override
        public void valueChanged(TreeSelectionEvent e) {
            Set<E> selected = getSelected();
            if (selected.isEmpty()) {
                Entity dsItem = datasource.getItemIfValid();
                datasource.setItem(null);

                if (dsItem == null) {
                    // in this case item change event will not be generated
                    refreshActionsState();
                }
            } else {
                Object item = selected.iterator().next();
                if (item != null) {
                    // reset selection and select new item
                    if (isMultiSelect()) {
                        datasource.setItem(null);
                    }
                    Entity newItem = selected.iterator().next();
                    Entity dsItem = datasource.getItemIfValid();
                    datasource.setItem(newItem);

                    if (Objects.equals(dsItem, newItem)) {
                        // in this case item change event will not be generated
                        refreshActionsState();
                    }
                } else {
                    // todo remove this if branch, should not happen
                    Entity dsItem = datasource.getItemIfValid();
                    datasource.setItem(null);

                    if (dsItem == null) {
                        // in this case item change event will not be generated
                        refreshActionsState();
                    }
                }
            }

            LookupSelectionChangeEvent selectionChangeEvent = new LookupSelectionChangeEvent(DesktopTree.this);
            for (LookupSelectionChangeListener listener : lookupSelectionChangeListeners) {
                listener.lookupValueChanged(selectionChangeEvent);
            }
        }
    }

    protected void refreshActionsState() {
        for (Action action : getActions()) {
            action.refreshState();
        }
    }

    protected JPopupMenu createPopupMenu() {
        JPopupMenu popup = new JPopupMenu();
        JMenuItem menuItem;
        for (final com.haulmont.cuba.gui.components.Action action : actionList) {
            if (StringUtils.isNotBlank(action.getCaption()) && action.isVisible()) {
                menuItem = new JMenuItem(action.getCaption());
                if (action.getIcon() != null) {
                    menuItem.setIcon(AppBeans.get(IconResolver.class).getIconResource(action.getIcon()));
                }
                if (action.getShortcutCombination() != null) {
                    menuItem.setAccelerator(
                            DesktopComponentsHelper.convertKeyCombination(action.getShortcutCombination()));
                }
                menuItem.setEnabled(action.isEnabled());
                menuItem.addActionListener(e -> action.actionPerform(DesktopTree.this));
                popup.add(menuItem);
            }
        }
        return popup;
    }

    @Override
    public void setStyleProvider(@Nullable StyleProvider<? super E> styleProvider) {
        // do nothing
    }

    @Override
    public void addStyleProvider(StyleProvider<? super E> styleProvider) {
        // do nothing
    }

    @Override
    public void removeStyleProvider(StyleProvider<? super E> styleProvider) {
        // do nothing
    }

    @Override
    public void setIconProvider(IconProvider<? super E> iconProvider) {
        // do nothing
    }

    @Override
    public void repaint() {
        // do nothing
    }

    @Override
    public void setEnterPressAction(Action action) {
        enterPressAction = action;
    }

    @Override
    public Action getEnterPressAction() {
        return enterPressAction;
    }
}