Package name.abuchen.portfolio.ui.views.taxonomy

Source Code of name.abuchen.portfolio.ui.views.taxonomy.AbstractNodeTreeViewer

package name.abuchen.portfolio.ui.views.taxonomy;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import java.util.UUID;

import name.abuchen.portfolio.model.AttributeType;
import name.abuchen.portfolio.model.AttributeTypes;
import name.abuchen.portfolio.model.Classification;
import name.abuchen.portfolio.model.Classification.Assignment;
import name.abuchen.portfolio.model.InvestmentVehicle;
import name.abuchen.portfolio.model.Named;
import name.abuchen.portfolio.model.Security;
import name.abuchen.portfolio.model.Values;
import name.abuchen.portfolio.ui.Messages;
import name.abuchen.portfolio.ui.PortfolioPlugin;
import name.abuchen.portfolio.ui.dnd.SecurityTransfer;
import name.abuchen.portfolio.ui.util.Column;
import name.abuchen.portfolio.ui.util.ColumnEditingSupport;
import name.abuchen.portfolio.ui.util.ColumnEditingSupport.ModificationListener;
import name.abuchen.portfolio.ui.util.ShowHideColumnHelper;
import name.abuchen.portfolio.ui.util.StringEditingSupport;
import name.abuchen.portfolio.ui.util.ViewerHelper;
import name.abuchen.portfolio.ui.util.WebLocationMenu;
import name.abuchen.portfolio.ui.views.columns.AttributeColumn;
import name.abuchen.portfolio.ui.views.columns.IsinColumn;
import name.abuchen.portfolio.ui.views.columns.NameColumn;
import name.abuchen.portfolio.ui.views.columns.NameColumn.NameColumnLabelProvider;
import name.abuchen.portfolio.ui.views.columns.NoteColumn;

import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.layout.TreeColumnLayout;
import org.eclipse.jface.viewers.ColumnLabelProvider;
import org.eclipse.jface.viewers.ColumnViewerToolTipSupport;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.TreeSelection;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerDropAdapter;
import org.eclipse.jface.window.ToolTip;
import org.eclipse.swt.SWT;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.DragSourceAdapter;
import org.eclipse.swt.dnd.DragSourceEvent;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.dnd.TransferData;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Shell;

/* package */abstract class AbstractNodeTreeViewer extends Page implements ModificationListener
{
    private static class ItemContentProvider implements ITreeContentProvider
    {
        private TaxonomyModel model;

        @Override
        public void inputChanged(Viewer viewer, Object oldInput, Object newInput)
        {
            model = (TaxonomyModel) newInput;
        }

        @Override
        public Object[] getElements(Object inputElement)
        {
            return new Object[] { model.getRootNode() };
        }

        @Override
        public boolean hasChildren(Object element)
        {
            return !((TaxonomyNode) element).getChildren().isEmpty();
        }

        @Override
        public Object[] getChildren(Object parentElement)
        {
            return ((TaxonomyNode) parentElement).getChildren().toArray();
        }

        @Override
        public Object getParent(Object element)
        {
            return ((TaxonomyNode) element).getParent();
        }

        @Override
        public void dispose()
        {}
    }

    private static class NodeDragListener extends DragSourceAdapter
    {
        private TreeViewer treeViewer;

        public NodeDragListener(TreeViewer treeViewer)
        {
            this.treeViewer = treeViewer;
        }

        @Override
        public void dragSetData(DragSourceEvent event)
        {
            TaxonomyNode selection = (TaxonomyNode) ((TreeSelection) treeViewer.getSelection()).getFirstElement();
            TaxonomyNodeTransfer.getTransfer().setTaxonomyNode(selection);
            Assignment assignment = selection.getAssignment();
            if (assignment != null && assignment.getInvestmentVehicle() instanceof Security)
                SecurityTransfer.getTransfer().setSecurity((Security) assignment.getInvestmentVehicle());
            else
                SecurityTransfer.getTransfer().setSecurity(null);

            event.data = selection;
        }

        @Override
        public void dragStart(DragSourceEvent event)
        {
            TaxonomyNode selection = (TaxonomyNode) ((TreeSelection) treeViewer.getSelection()).getFirstElement();
            event.doit = !selection.isRoot() && !selection.isUnassignedCategory();
        }
    }

    private static class NodeDropListener extends ViewerDropAdapter
    {
        private AbstractNodeTreeViewer viewer;

        public NodeDropListener(AbstractNodeTreeViewer viewer)
        {
            super(viewer.getNodeViewer());
            this.viewer = viewer;
        }

        @Override
        public boolean performDrop(Object data)
        {
            TaxonomyNode droppedNode = TaxonomyNodeTransfer.getTransfer().getTaxonomyNode();
            if (droppedNode == getCurrentTarget())
                return false;

            TaxonomyNode target = (TaxonomyNode) getCurrentTarget();

            // no categories allowed below the unassigned category
            if (target.isUnassignedCategory() && droppedNode.isClassification())
                return false;

            // save parent in order to refresh later
            TaxonomyNode droppedParent = droppedNode.getParent();

            switch (getCurrentLocation())
            {
                case ViewerDropAdapter.LOCATION_AFTER:
                    droppedNode.insertAfter(target);
                    viewer.onTaxnomyNodeEdited(droppedParent);
                    break;
                case ViewerDropAdapter.LOCATION_BEFORE:
                    droppedNode.insertBefore(target);
                    viewer.onTaxnomyNodeEdited(droppedParent);
                    break;
                case ViewerDropAdapter.LOCATION_ON:
                    // parent must not be dropped into child
                    if (target.getPath().contains(droppedNode))
                        break;

                    // target must not be dropped into same node
                    if (droppedParent.equals(target))
                        break;

                    droppedNode.moveTo(target);
                    viewer.onTaxnomyNodeEdited(droppedParent);
                    break;
                case ViewerDropAdapter.LOCATION_NONE:
                    break;
                default:
                    break;
            }

            return true;
        }

        @Override
        public boolean validateDrop(Object target, int operation, TransferData transferType)
        {
            if (!(target instanceof TaxonomyNode))
                return false;

            TaxonomyNode targetNode = (TaxonomyNode) target;

            int location = determineLocation(this.getCurrentEvent());

            if (targetNode.isClassification())
                return true;
            else if (targetNode.isAssignment())
                return location == LOCATION_AFTER || location == LOCATION_BEFORE;
            else
                return false;
        }
    }

    protected static final String MENU_GROUP_DEFAULT_ACTIONS = "defaultActions"; //$NON-NLS-1$
    protected static final String MENU_GROUP_CUSTOM_ACTIONS = "customActions"; //$NON-NLS-1$
    protected static final String MENU_GROUP_DELETE_ACTIONS = "deleteActions"; //$NON-NLS-1$

    private TreeViewer nodeViewer;
    private ShowHideColumnHelper support;

    private boolean isFirstView = true;

    public AbstractNodeTreeViewer(TaxonomyModel model, TaxonomyNodeRenderer renderer)
    {
        super(model, renderer);
    }

    protected final TreeViewer getNodeViewer()
    {
        return nodeViewer;
    }

    @Override
    public void onModified(Object element, Object newValue, Object oldValue)
    {
        onTaxnomyNodeEdited((TaxonomyNode) element);
    }

    public void onWeightModified(Object element, Object newValue, Object oldValue)
    {
        TaxonomyNode node = (TaxonomyNode) element;

        if (node.getWeight() > Classification.ONE_HUNDRED_PERCENT)
            node.setWeight(Classification.ONE_HUNDRED_PERCENT);
        else if (node.getWeight() < 0)
            node.setWeight(0);

        if (node.isAssignment())
        {
            int oldWeight = (Integer) oldValue;
            doChangeAssignmentWeight(node, oldWeight);
        }

        onModified(element, newValue, oldValue);
    }

    @Override
    public void showConfigMenu(Shell shell)
    {
        support.showHideShowColumnsMenu(shell);
    }

    public final Control createControl(Composite parent)
    {
        Composite container = new Composite(parent, SWT.NONE);
        TreeColumnLayout layout = new TreeColumnLayout();
        container.setLayout(layout);

        nodeViewer = new TreeViewer(container, SWT.FULL_SELECTION);

        ColumnEditingSupport.prepare(nodeViewer);
        ColumnViewerToolTipSupport.enableFor(nodeViewer, ToolTip.NO_RECREATE);

        support = new ShowHideColumnHelper(getClass().getSimpleName() + '#' + getModel().getTaxonomy().getId(),
                        getPreferenceStore(), nodeViewer, layout);

        addColumns(support);

        support.createColumns();

        nodeViewer.getTree().setHeaderVisible(true);
        nodeViewer.getTree().setLinesVisible(true);
        nodeViewer.setContentProvider(new ItemContentProvider());

        nodeViewer.addDragSupport(DND.DROP_MOVE | DND.DROP_COPY, new Transfer[] { TaxonomyNodeTransfer.getTransfer(),
                        SecurityTransfer.getTransfer() }, new NodeDragListener(nodeViewer));
        nodeViewer.addDropSupport(DND.DROP_MOVE | DND.DROP_COPY, new Transfer[] { TaxonomyNodeTransfer.getTransfer() },
                        new NodeDropListener(this));

        nodeViewer.setInput(getModel());

        ViewerHelper.pack(nodeViewer);

        ViewerHelper.attachContextMenu(nodeViewer, new IMenuListener()
        {
            @Override
            public void menuAboutToShow(IMenuManager manager)
            {
                fillContextMenu(manager);
            }
        });

        return container;
    }

    protected abstract void addColumns(ShowHideColumnHelper support);

    protected void addDimensionColumn(ShowHideColumnHelper support)
    {
        Column column = new NameColumn("txname", Messages.ColumnLevels, SWT.NONE, 400); //$NON-NLS-1$
        column.setLabelProvider(new NameColumnLabelProvider()
        {
            @Override
            public Image getImage(Object e)
            {
                if (((TaxonomyNode) e).isUnassignedCategory())
                    return PortfolioPlugin.image(PortfolioPlugin.IMG_UNASSIGNED_CATEGORY);
                return super.getImage(e);
            }
        });
        new StringEditingSupport(Named.class, "name") //$NON-NLS-1$
        {
            @Override
            public boolean canEdit(Object element)
            {
                if (((TaxonomyNode) element).isUnassignedCategory())
                    return false;
                else
                    return super.canEdit(element);
            }
        }.setMandatory(true).addListener(this).attachTo(column);
        column.setRemovable(false);
        support.addColumn(column);

        column = new IsinColumn();
        column.getEditingSupport().addListener(this);
        column.setSorter(null);
        column.setVisible(false);
        support.addColumn(column);

        column = new NoteColumn();
        column.getEditingSupport().addListener(this);
        column.setSorter(null);
        column.setVisible(false);
        support.addColumn(column);
    }

    protected void addActualColumns(ShowHideColumnHelper support)
    {
        Column column = new Column("act%", Messages.ColumnActualPercent, SWT.RIGHT, 60); //$NON-NLS-1$
        column.setLabelProvider(new ColumnLabelProvider()
        {
            @Override
            public String getText(Object element)
            {
                TaxonomyNode node = (TaxonomyNode) element;
                // actual %
                // --> root is compared to target = total assets
                long actual = node.getActual();
                long base = node.getParent() == null ? node.getActual() : node.getParent().getActual();

                if (base == 0d)
                    return Values.Percent.format(0d);
                else
                    return Values.Percent.format(((double) actual / (double) base));
            }
        });
        support.addColumn(column);

        column = new Column("act", Messages.ColumnActualValue, SWT.RIGHT, 100); //$NON-NLS-1$
        column.setLabelProvider(new ColumnLabelProvider()
        {
            @Override
            public String getText(Object element)
            {
                TaxonomyNode node = (TaxonomyNode) element;
                return Values.Amount.format(node.getActual());
            }
        });
        support.addColumn(column);
    }

    protected void addAdditionalColumns(ShowHideColumnHelper support)
    {
        for (final AttributeType attribute : AttributeTypes.available(Security.class))
        {
            Column column = new AttributeColumn(attribute);
            column.setVisible(false);
            column.setSorter(null);
            column.getEditingSupport().addListener(this);
            support.addColumn(column);
        }
    }

    private void expandNodes()
    {
        List<TaxonomyNode> expanded = new ArrayList<TaxonomyNode>();
        LinkedList<TaxonomyNode> stack = new LinkedList<TaxonomyNode>();
        stack.push(getModel().getRootNode());
        while (!stack.isEmpty())
        {
            TaxonomyNode node = stack.pop();
            if (node.isClassification() && !node.getClassification().getChildren().isEmpty())
            {
                expanded.add(node);
                stack.addAll(node.getChildren());
            }
        }

        nodeViewer.getTree().setRedraw(false);
        try
        {
            nodeViewer.setExpandedElements(expanded.toArray());
        }
        finally
        {
            nodeViewer.getTree().setRedraw(true);
        }
    }

    protected void onTaxnomyNodeEdited(TaxonomyNode node)
    {
        getModel().recalculate();
        getModel().fireTaxonomyModelChange(node);
    }

    @Override
    public void nodeChange(TaxonomyNode node)
    {
        if (!nodeViewer.getTree().isDisposed())
            nodeViewer.refresh();
    }

    @Override
    public void beforePage()
    {
        if (isFirstView)
        {
            expandNodes();
            isFirstView = false;
        }
    }

    @Override
    public void afterPage()
    {}

    protected void fillContextMenu(IMenuManager manager)
    {
        final TaxonomyNode node = (TaxonomyNode) ((IStructuredSelection) nodeViewer.getSelection()).getFirstElement();
        if (node == null)
            return;

        if (node.isUnassignedCategory())
            return;

        // allow inherited views to contribute to the context menu
        manager.add(new Separator(MENU_GROUP_CUSTOM_ACTIONS));

        manager.add(new Separator(MENU_GROUP_DEFAULT_ACTIONS));

        if (node.isClassification())
        {
            manager.add(new Action(Messages.MenuTaxonomyClassificationCreate)
            {
                @Override
                public void run()
                {
                    doAddClassification(node);
                }
            });

            TaxonomyNode unassigned = getModel().getUnassignedNode();
            if (!unassigned.getChildren().isEmpty())
            {
                MenuManager subMenu = new MenuManager(Messages.MenuTaxonomyMakeAssignment);
                for (final TaxonomyNode assignment : unassigned.getChildren())
                {
                    String label = assignment.getName();

                    if (assignment.getWeight() < Classification.ONE_HUNDRED_PERCENT)
                        label += " (" + Values.Weight.format(assignment.getWeight()) + "%)"; //$NON-NLS-1$ //$NON-NLS-2$

                    subMenu.add(new Action(label)
                    {
                        @Override
                        public void run()
                        {
                            assignment.moveTo(node);
                            nodeViewer.setExpandedState(node, true);
                            onTaxnomyNodeEdited(node);
                        }
                    });
                }
                manager.add(subMenu);
            }

            manager.add(new Separator());

            MenuManager sorting = new MenuManager(Messages.MenuTaxonomySortTreeBy);
            sorting.add(new Action(Messages.MenuTaxonomySortByTypName)
            {
                @Override
                public void run()
                {
                    doSort(node, true);
                }
            });
            sorting.add(new Action(Messages.MenuTaxonomySortByName)
            {
                @Override
                public void run()
                {
                    doSort(node, false);
                }
            });

            manager.add(sorting);

            if (!node.isRoot())
            {
                manager.add(new Separator(MENU_GROUP_DELETE_ACTIONS));
                manager.add(new Action(Messages.MenuTaxonomyClassificationDelete)
                {
                    @Override
                    public void run()
                    {
                        doDeleteClassification(node);
                    }
                });
            }
        }
        else
        {
            // node is assignment, but not in unassigned category
            if (!node.getParent().isUnassignedCategory())
            {
                manager.add(new Action(Messages.MenuTaxonomyAssignmentRemove)
                {
                    @Override
                    public void run()
                    {
                        int oldWeight = node.getWeight();
                        node.setWeight(0);
                        doChangeAssignmentWeight(node, oldWeight);
                        onTaxnomyNodeEdited(getModel().getRootNode());
                    }
                });

                Security security = node.getBackingSecurity();
                if (security != null)
                {
                    manager.add(new Separator());
                    manager.add(new WebLocationMenu(security));
                }
            }
        }
    }

    private void doAddClassification(TaxonomyNode parent)
    {
        Classification newClassification = new Classification(null, UUID.randomUUID().toString(),
                        Messages.LabelNewClassification);

        TaxonomyNode newNode = parent.addChild(newClassification);

        nodeViewer.setExpandedState(parent, true);
        onTaxnomyNodeEdited(parent);
        nodeViewer.editElement(newNode, 0);
    }

    private void doDeleteClassification(TaxonomyNode node)
    {
        if (node.isRoot() || node.isUnassignedCategory())
            return;

        node.getParent().removeChild(node);

        node.accept(new TaxonomyModel.NodeVisitor()
        {
            @Override
            public void visit(TaxonomyNode node)
            {
                if (node.isAssignment())
                    node.moveTo(getModel().getUnassignedNode());
            }
        });

        onTaxnomyNodeEdited(getModel().getRootNode());
    }

    private void doChangeAssignmentWeight(TaxonomyNode node, int oldWeight)
    {
        int change = oldWeight - node.getWeight();

        if (change == 0) // was 'fixed' after editing, e.g. was >= 100%
            return;

        // if new weight = 0, then remove assignment

        if (node.getWeight() == 0)
            node.getParent().removeChild(node);

        // change total weight as recorded in the model

        InvestmentVehicle investmentVehicle = node.getAssignment().getInvestmentVehicle();
        final int totalWeight = getModel().getWeightByInvestmentVehicle(investmentVehicle) - change;
        getModel().setWeightByInvestmentVehicle(investmentVehicle, totalWeight);

        // check if change is fixing weight errors -> no new unassigned vehicles

        change = Math.min(change, Classification.ONE_HUNDRED_PERCENT - totalWeight);
        if (change == 0)
            return;

        // change existing unassigned node *or* create new unassigned node

        TaxonomyNode unassigned = getModel().getUnassignedNode().getChildByInvestmentVehicle(investmentVehicle);

        if (unassigned != null)
        {
            // change existing node in unassigned category

            int newWeight = unassigned.getWeight() + change;
            if (newWeight <= 0)
            {
                getModel().getUnassignedNode().removeChild(unassigned);
                getModel().setWeightByInvestmentVehicle(investmentVehicle, totalWeight - unassigned.getWeight());
            }
            else
            {
                unassigned.setWeight(newWeight);
                getModel().setWeightByInvestmentVehicle(investmentVehicle, totalWeight + change);
            }
        }
        else if (change > 0)
        {
            // create a new node, but only if change is positive

            Assignment assignment = new Assignment(investmentVehicle);
            assignment.setWeight(change);
            getModel().getUnassignedNode().addChild(assignment);
            getModel().setWeightByInvestmentVehicle(investmentVehicle, totalWeight + change);
        }

        // do not fire model change -> called within modification listener
    }

    private void doSort(TaxonomyNode node, final boolean byType)
    {
        Collections.sort(node.getChildren(), new Comparator<TaxonomyNode>()
        {
            @Override
            public int compare(TaxonomyNode node1, TaxonomyNode node2)
            {
                // unassigned category always stays at the end of the list
                if (node1.isUnassignedCategory())
                    return 1;
                if (node2.isUnassignedCategory())
                    return -1;

                if (byType && node1.isClassification() && !node2.isClassification())
                    return -1;
                if (byType && !node1.isClassification() && node2.isClassification())
                    return 1;

                return node1.getName().compareToIgnoreCase(node2.getName());
            }
        });

        int rank = 0;
        for (TaxonomyNode child : node.getChildren())
            child.setRank(rank++);

        getModel().fireTaxonomyModelChange(node);
    }
}
TOP

Related Classes of name.abuchen.portfolio.ui.views.taxonomy.AbstractNodeTreeViewer

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.