org.robotframework.ide.eclipse.main.plugin.hyperlink.TableHyperlinksSupport.java Source code

Java tutorial

Introduction

Here is the source code for org.robotframework.ide.eclipse.main.plugin.hyperlink.TableHyperlinksSupport.java

Source

/*
 * Copyright 2016 Nokia Solutions and Networks
 * Licensed under the Apache License, Version 2.0,
 * see license.txt file for details.
 */
package org.robotframework.ide.eclipse.main.plugin.hyperlink;

import static com.google.common.collect.Lists.newArrayList;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;

import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.layout.GridLayoutFactory;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.hyperlink.IHyperlink;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.StyledString;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.ViewerColumnsFactory;
import org.eclipse.nebula.widgets.nattable.NatTable;
import org.eclipse.nebula.widgets.nattable.layer.cell.ILayerCell;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseMoveListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Cursor;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.TableItem;
import org.robotframework.ide.eclipse.main.plugin.hyperlink.detectors.ITableHyperlinksDetector;
import org.robotframework.red.nattable.TableCellStringData;
import org.robotframework.red.nattable.TableCellsStrings;
import org.robotframework.red.swt.SwtThread;
import org.robotframework.red.viewers.RedCommonLabelProvider;
import org.robotframework.red.viewers.Selections;
import org.robotframework.red.viewers.StructuredContentProvider;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Iterables;
import com.google.common.collect.Range;

public class TableHyperlinksSupport {

    private final NatTable table;

    private final TableCellsStrings tableStrings;

    private final List<ITableHyperlinksDetector> detectors = new ArrayList<>();

    private Shell infoShell;
    private List<IHyperlink> hyperlinks = new ArrayList<>();
    private TableCellStringData currentData = null;
    private Cursor cursor = null;

    public static TableHyperlinksSupport enableHyperlinksInTable(final NatTable table,
            final TableCellsStrings tableStrings) {

        final TableHyperlinksSupport detector = new TableHyperlinksSupport(table, tableStrings);
        table.addMouseListener(detector.new HyperlinksClickListener());
        table.addMouseMoveListener(detector.new HyperlinksMouseMoveListener());

        final Display display = table.getDisplay();
        final HyperlinksKeyListener filter = detector.new HyperlinksKeyListener();
        display.addFilter(SWT.KeyUp, filter);
        table.addDisposeListener(new DisposeListener() {
            @Override
            public void widgetDisposed(final DisposeEvent e) {
                display.removeFilter(SWT.KeyUp, filter);
            }
        });
        return detector;
    }

    private TableHyperlinksSupport(final NatTable table, final TableCellsStrings tableStrings) {
        this.tableStrings = tableStrings;
        this.table = table;
    }

    public void addDetectors(final ITableHyperlinksDetector... detectors) {
        this.detectors.addAll(newArrayList(detectors));
    }

    public void removeDetector(final ITableHyperlinksDetector detector) {
        this.detectors.remove(detector);
    }

    private void removeHyperlink() {
        if (infoShell != null && !infoShell.isDisposed()) {
            infoShell.close();
            infoShell.dispose();
            infoShell = null;
        }
        if (cursor != null) {
            table.getShell().setCursor(null);
            cursor.dispose();
            cursor = null;
        }
        if (currentData != null) {
            currentData.removeHyperlink();
            table.redraw();
        }
        hyperlinks.clear();
    }

    private void openHyperlink(final IHyperlink linkToOpen) {
        removeHyperlink();

        SwtThread.asyncExec(new Runnable() {

            @Override
            public void run() {
                linkToOpen.open();
            }
        });
    }

    public List<ITableHyperlinksDetector> getDetectors() {
        return detectors;
    }

    @VisibleForTesting
    static Optional<IRegion> getMergedHyperlinkRegion(final Collection<IHyperlink> hyperlinks) {
        if (hyperlinks.isEmpty()) {
            return Optional.empty();
        }
        IRegion hyperlinkRegion = Iterables.getFirst(hyperlinks, null).getHyperlinkRegion();
        for (final IHyperlink link : hyperlinks) {
            hyperlinkRegion = merge(hyperlinkRegion, link.getHyperlinkRegion());
        }
        return Optional.of(hyperlinkRegion);
    }

    private static IRegion merge(final IRegion region1, final IRegion region2) {
        final int startOffset = Integer.min(region1.getOffset(), region2.getOffset());
        final int endOffset = Integer.max(region1.getOffset() + region1.getLength(),
                region2.getOffset() + region2.getLength());
        return new Region(startOffset, endOffset - startOffset);
    }

    private class HyperlinksKeyListener implements Listener {

        @Override
        public void handleEvent(final Event event) {
            if (event.keyCode == SWT.CTRL) {
                removeHyperlink();
            }
        }
    }

    private class HyperlinksClickListener extends MouseAdapter {

        @Override
        public void mouseUp(final MouseEvent e) {
            if (!hyperlinks.isEmpty()) {
                openHyperlink(hyperlinks.get(0));
            }
        }
    }

    private class HyperlinksMouseMoveListener implements MouseMoveListener {

        @Override
        public void mouseMove(final MouseEvent e) {

            if (e.stateMask != SWT.CTRL || detectors.isEmpty()) {
                // no detectors or CTRL is not pressed
                removeHyperlink();
                return;
            }

            final int column = table.getColumnPositionByX(e.x);
            final int row = table.getRowPositionByY(e.y);
            final ILayerCell cell = table.getCellByPosition(column, row);
            if (cell == null) {
                // no cell for given table coordinates
                removeHyperlink();
                return;
            }
            final String actualLabel = (String) cell.getDataValue();

            final TableCellStringData textData = tableStrings.get(column, row);
            if (textData == null) {
                // no info about labels drawn in this cell
                removeHyperlink();
                return;
            }

            final int index = textData.getCharacterIndexFrom(e.x, e.y);
            if (index < 0) {
                // mouse position is outside of drawn label

                if (isPopupOpen()) {
                    final Point popupLocation = infoShell.getLocation();
                    final Point popupSize = infoShell.getSize();
                    final Rectangle popupRectangle = new Rectangle(popupLocation.x, popupLocation.y, popupSize.x,
                            popupSize.y);

                    final Point labelLocation = table.toDisplay(textData.getCoordinate());
                    final Rectangle labelRectangle = new Rectangle(labelLocation.x, labelLocation.y,
                            textData.getExtent().x, textData.getExtent().y);

                    if (!popupRectangle.union(labelRectangle).contains(table.toDisplay(e.x, e.y))) {
                        // mouse is moving outside of popup, so we need to close and remove
                        removeHyperlink();
                    }
                } else {
                    removeHyperlink();
                }
                return;
            }

            final Range<Integer> currentHyperlinkRegion = textData.getHyperlinkRegion();
            if (currentHyperlinkRegion != null && currentHyperlinkRegion.lowerEndpoint() <= index
                    && index <= currentHyperlinkRegion.upperEndpoint()) {
                // no need to remove hyperlinks, we're moving inside place which already has link
                return;
            } else if (currentHyperlinkRegion != null && isPopupOpen()) {
                // we're over the label, outside the generated link, but the popup is open, so we
                // don't want to recalculate hyperlinks
                return;
            }

            hyperlinks = collectHyperlinks(column, row, actualLabel, index);

            final Optional<IRegion> hyperlinkRegion = getMergedHyperlinkRegion(hyperlinks);
            if (!hyperlinkRegion.isPresent()) {
                // there is no hyperlink region
                removeHyperlink();
                return;
            }

            if (currentData != null) {
                currentData.removeHyperlink();
            }
            textData.createHyperlinkAt(hyperlinkRegion.get().getOffset(),
                    hyperlinkRegion.get().getOffset() + hyperlinkRegion.get().getLength());
            currentData = textData;

            if (hyperlinks.size() > 1) {
                openChoicePopup(calculatePopupLocation(cell, textData));
            }
            changeCursor();
            table.redraw();
        }

        private boolean isPopupOpen() {
            return infoShell != null && !infoShell.isDisposed() && infoShell.isVisible();
        }

        private List<IHyperlink> collectHyperlinks(final int column, final int row, final String actualLabel,
                final int index) {
            // 1 is substracted due to column/row headers
            final List<IHyperlink> hyperlinks = new ArrayList<>();
            for (final ITableHyperlinksDetector detector : detectors) {
                hyperlinks.addAll(detector.detectHyperlinks(row - 1, column - 1, actualLabel, index));
            }
            return hyperlinks;
        }

        private Point calculatePopupLocation(final ILayerCell cell, final TableCellStringData textData) {
            final int x = textData.getCoordinate().x;
            final int y = cell.getBounds().y + cell.getBounds().height;
            return table.toDisplay(x, y);
        }

        private void openChoicePopup(final Point location) {
            if (infoShell != null && !infoShell.isDisposed()) {
                infoShell.close();
                infoShell.dispose();
            }
            infoShell = new Shell(table.getShell(), SWT.TOOL | SWT.ON_TOP);
            infoShell.setLocation(location);
            GridLayoutFactory.fillDefaults().applyTo(infoShell);

            final Composite comp = new Composite(infoShell, SWT.NONE);
            comp.setBackground(table.getBackground());
            GridLayoutFactory.fillDefaults().margins(3, 5).applyTo(comp);
            GridDataFactory.fillDefaults().grab(true, false).applyTo(comp);

            final TableViewer viewer = new TableViewer(comp, SWT.SINGLE | SWT.NO_SCROLL);
            viewer.getTable().setHeaderVisible(false);
            viewer.getTable().setLinesVisible(false);
            viewer.setContentProvider(new HyperlinksContentProvider());
            viewer.getTable().addMouseMoveListener(new MouseMoveListener() {

                @Override
                public void mouseMove(final MouseEvent e) {
                    if (viewer.getTable().equals(e.getSource())) {
                        final TableItem item = viewer.getTable().getItem(new Point(e.x, e.y));
                        if (item != null) {
                            viewer.getTable().setSelection(new TableItem[] { item });
                        }
                    }
                }
            });
            viewer.getTable().addMouseListener(new MouseAdapter() {

                @Override
                public void mouseUp(final MouseEvent e) {
                    if (viewer.getTable().getSelectionCount() < 1 || e.button != 1) {
                        return;
                    }

                    if (viewer.getTable().equals(e.getSource())) {
                        final TableItem item = viewer.getTable().getItem(new Point(e.x, e.y));
                        final TableItem selection = viewer.getTable().getSelection()[0];
                        if (selection.equals(item)) {
                            openHyperlink((IHyperlink) item.getData());
                        }
                    }
                }
            });
            viewer.getTable().addSelectionListener(new SelectionAdapter() {

                @Override
                public void widgetDefaultSelected(final SelectionEvent e) {
                    openHyperlink(Selections.getSingleElement((IStructuredSelection) viewer.getSelection(),
                            IHyperlink.class));
                }
            });

            ViewerColumnsFactory.newColumn("").labelsProvidedBy(new HyperlinksLabelProvider())
                    .shouldGrabAllTheSpaceLeft(true).withMinWidth(50).createFor(viewer);

            viewer.setInput(hyperlinks);
            GridDataFactory.fillDefaults().grab(true, true).applyTo(viewer.getTable());

            viewer.getTable().select(0);
            viewer.getTable().getColumn(0).pack();
            viewer.getTable().pack();

            infoShell.pack();
            infoShell.setVisible(true);
        }

        private void changeCursor() {
            if (cursor == null) {
                cursor = new Cursor(table.getDisplay(), SWT.CURSOR_HAND);
            }
            table.getShell().setCursor(cursor);
        }
    }

    private static class HyperlinksContentProvider extends StructuredContentProvider {

        @Override
        public Object[] getElements(final Object inputElement) {
            final List<?> hyperlinks = (List<?>) inputElement;
            return hyperlinks.toArray();
        }
    }

    private static class HyperlinksLabelProvider extends RedCommonLabelProvider {
        @Override
        public StyledString getStyledText(final Object element) {
            final IHyperlink hyperlink = (IHyperlink) element;
            return new StyledString(hyperlink.getHyperlinkText());
        }
    }
}