/********************************************************************************************* * * 'DisplayOverlay.java, in plugin ummisco.gama.ui.experiment, is part of the source code of the GAMA modeling and * simulation platform. (v. 1.8.1) * * (c) 2007-2020 UMI 209 UMMISCO IRD/UPMC & Partners * * Visit https://github.com/gama-platform/gama for license information and developers contact. * * **********************************************************************************************/ package ummisco.gama.ui.views.displays; import java.util.List; import java.util.Timer; import java.util.TimerTask; import org.eclipse.swt.SWT; import org.eclipse.swt.events.ControlEvent; import org.eclipse.swt.events.ControlListener; import org.eclipse.swt.events.MouseAdapter; import org.eclipse.swt.events.MouseEvent; import org.eclipse.swt.events.MouseListener; import org.eclipse.swt.events.ShellAdapter; import org.eclipse.swt.events.ShellEvent; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.graphics.Path; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.layout.FillLayout; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Canvas; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Shell; import org.eclipse.ui.IPartListener2; import org.eclipse.ui.IPartService; import org.eclipse.ui.IWorkbenchPart; import org.eclipse.ui.IWorkbenchPartReference; import org.eclipse.ui.IWorkbenchPartSite; import msi.gama.common.geometry.Envelope3D; import msi.gama.common.interfaces.IDisplaySurface; import msi.gama.common.interfaces.IGui; import msi.gama.common.interfaces.IOverlayProvider; import msi.gama.common.interfaces.IUpdaterTarget; import msi.gama.outputs.LayeredDisplayOutput; import msi.gama.outputs.layers.OverlayStatement.OverlayInfo; import msi.gaml.operators.Maths; import ummisco.gama.dev.utils.DEBUG; import ummisco.gama.ui.resources.GamaColors; import ummisco.gama.ui.resources.IGamaColors; import ummisco.gama.ui.utils.WorkbenchHelper; /** * The class DisplayOverlay. * * @author drogoul * @since 19 august 2013 * */ public class DisplayOverlay implements IUpdaterTarget<OverlayInfo> { static { DEBUG.OFF(); } Label coord, zoom, left, center, right; StringBuilder text = new StringBuilder(); Canvas scalebar; volatile boolean isBusy; private final Shell popup; private boolean visible = false; final LayeredDisplayView view; protected final Composite referenceComposite; // private final Shell parentShell; final boolean createExtraInfo; Timer timer = new Timer(); public class FPSTask extends TimerTask { @Override public void run() { WorkbenchHelper.asyncRun(() -> { if (!zoom.isDisposed()) { text.setLength(0); getOverlayZoomInfo(text); zoom.setText(text.toString()); } }); } } class OverlayListener extends ShellAdapter implements ControlListener { @Override public void controlMoved(final ControlEvent e) { relocate(); resize(); } @Override public void controlResized(final ControlEvent e) { relocate(); resize(); } @Override public void shellClosed(final ShellEvent e) { close(); } } public DisplayOverlay(final LayeredDisplayView view, final Composite c, final IOverlayProvider<OverlayInfo> provider) { this.createExtraInfo = provider != null; this.view = view; final IPartService ps = ((IWorkbenchPart) view).getSite().getService(IPartService.class); ps.addPartListener(pl2); referenceComposite = c; // parentShell = c.getShell(); popup = new Shell(c.getShell(), SWT.NO_TRIM | SWT.NO_FOCUS); popup.setAlpha(140); final FillLayout layout = new FillLayout(); layout.type = SWT.VERTICAL; layout.spacing = 10; popup.setLayout(layout); popup.setBackground(IGamaColors.BLACK.color()); createPopupControl(); popup.setAlpha(140); popup.layout(); c.getShell().addShellListener(listener); // parentShell.addControlListener(listener); c.addControlListener(listener); if (provider != null) { provider.setTarget(new ThreadedOverlayUpdater(this), view.getDisplaySurface()); } // if (GamaPreferences.Displays.CORE_SHOW_FPS.getValue()) { timer.schedule(new FPSTask(), 0, 1000); // } } // public void relocateOverlay(final Shell newShell) { // if (popup.setParent(newShell)) { // // DEBUG.LOG("Relocating overlay"); // popup.moveAbove(referenceComposite); // } // } private Label label(final Composite c, final int horizontalAlign) { final Label l = new Label(c, SWT.None); l.setForeground(IGamaColors.WHITE.color()); l.setBackground(IGamaColors.BLACK.color()); l.setText(" "); l.setLayoutData(infoData(horizontalAlign)); l.addMouseListener(toggleListener); return l; } private GridData infoData(final int horizontalAlign) { final GridData data = new GridData(horizontalAlign, SWT.CENTER, true, false); data.minimumHeight = 24; data.heightHint = 24; return data; } protected void createPopupControl() { // overall panel final Shell top = getPopup(); final GridLayout layout = new GridLayout(3, true); layout.horizontalSpacing = 0; layout.verticalSpacing = 0; layout.marginWidth = 5; layout.marginHeight = 5; top.setLayout(layout); top.setBackground(IGamaColors.BLACK.color()); if (createExtraInfo) { // left overlay info left = label(top, SWT.LEFT); // center overlay info center = label(top, SWT.CENTER); // right overlay info right = label(top, SWT.RIGHT); } // coordinates overlay info coord = label(top, SWT.LEFT); // zoom overlay info zoom = label(top, SWT.CENTER); // scalebar overlay info scalebar = new Canvas(top, SWT.None); scalebar.setVisible(true); final GridData scaleData = new GridData(SWT.RIGHT, SWT.CENTER, true, false); scaleData.minimumWidth = 140; scaleData.widthHint = 140; scaleData.minimumHeight = 24; scaleData.heightHint = 24; scalebar.setLayoutData(scaleData); scalebar.setBackground(IGamaColors.BLACK.color()); scalebar.addPaintListener(e -> paintScale(e.gc)); top.addMouseListener(toggleListener); scalebar.addMouseListener(toggleListener); top.layout(); } void paintScale(final GC gc) { gc.setBackground(IGamaColors.BLACK.color()); final int BAR_WIDTH = 1; final int BAR_HEIGHT = 8; final int x = 0; final int y = 0; final int margin = 20; final int width = scalebar.getBounds().width - 2 * margin; final int height = scalebar.getBounds().height; final int barStartX = x + 1 + BAR_WIDTH / 2 + margin; final int barStartY = y + height - BAR_HEIGHT / 2; final Path path = new Path(WorkbenchHelper.getDisplay()); path.moveTo(barStartX, barStartY - BAR_HEIGHT + 2); path.lineTo(barStartX, barStartY + 2); path.moveTo(barStartX, barStartY - BAR_HEIGHT / 2 + 2); path.lineTo(barStartX + width, barStartY - BAR_HEIGHT / 2 + 2); path.moveTo(barStartX + width, barStartY - BAR_HEIGHT + 2); path.lineTo(barStartX + width, barStartY + 2); gc.setForeground(IGamaColors.WHITE.color()); gc.setLineStyle(SWT.LINE_SOLID); gc.setLineWidth(BAR_WIDTH); gc.drawPath(path); gc.setFont(coord.getFont()); drawStringCentered(gc, "0", barStartX, barStartY - 6, false); drawStringCentered(gc, getScaleRight(), barStartX + width, barStartY - 6, false); path.dispose(); } private String getScaleRight() { final double real = getValueOfOnePixelInModelUnits() * 100; // DEBUG.LOG("GetScaleRight " + real); if (real > 1000) { return String.format("%.1fkm", real / 1000d); } else if (real < 0.001) { return String.format("%dmm", (int) real * 1000); } else if (real < 0.01) { return String.format("%dcm", (int) (real * 100)); } else { return String.format("%dm", (int) real); } } Runnable doHide = () -> hide(); Runnable doDisplay = () -> display(); private final IPartListener2 pl2 = new IPartListener2() { @Override public void partActivated(final IWorkbenchPartReference partRef) { final IWorkbenchPart part = partRef.getPart(false); if (view.equals(part)) { WorkbenchHelper.run(doDisplay); } } @Override public void partBroughtToTop(final IWorkbenchPartReference partRef) {} @Override public void partClosed(final IWorkbenchPartReference partRef) { final IWorkbenchPart part = partRef.getPart(false); if (view.equals(part)) { close(); } } @Override public void partDeactivated(final IWorkbenchPartReference partRef) { final IWorkbenchPart part = partRef.getPart(false); if (view.equals(part) && !referenceComposite.isDisposed() && !referenceComposite.isVisible()) { WorkbenchHelper.run(doHide); } } @Override public void partOpened(final IWorkbenchPartReference partRef) {} @Override public void partHidden(final IWorkbenchPartReference partRef) { final IWorkbenchPart part = partRef.getPart(false); if (view.equals(part)) { WorkbenchHelper.run(doHide); } } @Override public void partVisible(final IWorkbenchPartReference partRef) { final IWorkbenchPart part = partRef.getPart(false); if (view.equals(part)) { WorkbenchHelper.run(doDisplay); } } @Override public void partInputChanged(final IWorkbenchPartReference partRef) {} }; OverlayListener listener = new OverlayListener(); protected final MouseListener toggleListener = new MouseAdapter() { @Override public void mouseUp(final MouseEvent e) { setVisible(false); } }; @Override public boolean isBusy() { return isBusy; } public void update() { if (isBusy) { return; } isBusy = true; try { if (getPopup().isDisposed()) { return; } if (!coord.isDisposed()) { try { text.setLength(0); getOverlayCoordInfo(text); coord.setText(text.toString()); } catch (final Exception e) { coord.setText("Not initialized yet"); } } if (!zoom.isDisposed()) { try { text.setLength(0); getOverlayZoomInfo(text); zoom.setText(text.toString()); } catch (final Exception e) { DEBUG.OUT("Error in updating overlay: " + e.getMessage()); zoom.setText("Not initialized yet"); } } if (!scalebar.isDisposed()) { scalebar.redraw(); scalebar.update(); } getPopup().layout(true); } finally { isBusy = false; } } protected Point getLocation() { final Rectangle r = referenceComposite.getClientArea(); final Point p = referenceComposite.toDisplay(r.x, r.y); final int x = p.x; final int y = p.y + r.height - (createExtraInfo ? 56 : 32); // DEBUG.OUT("Location of overlay = " + x + " " + y + " <> client area dans Display : " + r); return new Point(x, y); } protected Point getSize() { final Point s = referenceComposite.getSize(); return new Point(s.x, -1); } private void drawStringCentered(final GC gc, final String string, final int xCenter, final int yBase, final boolean filled) { final Point extent = gc.textExtent(string); final int xx = xCenter - extent.x / 2; gc.drawText(string, xx, yBase - extent.y, !filled); } // public void displayScale(final Boolean newValue) { // scalebar.setVisible(newValue); // } /** * @param left2 * @param createColor */ private void setForeground(final Label label, final Color color) { if (label == null || label.isDisposed()) { return; } final Color c = label.getForeground(); label.setForeground(color); if (c != IGamaColors.WHITE.color() && c != color) { c.dispose(); } } /** * Method updateWith() * * @see msi.gama.gui.swt.controls.IUpdaterTarget#updateWith(java.lang.Object) */ @Override public void updateWith(final OverlayInfo m) { final String[] infos = m.infos; final List<int[]> colors = m.colors; if (infos[0] != null) { left.setText(infos[0]); if (colors != null) { setForeground(left, GamaColors.get(colors.get(0)).color()); } } if (infos[1] != null) { center.setText(infos[1]); if (colors != null) { setForeground(center, GamaColors.get(colors.get(1)).color()); } } if (infos[2] != null) { right.setText(infos[2]); if (colors != null) { setForeground(right, GamaColors.get(colors.get(2)).color()); } } getPopup().layout(true); } /** * Method getCurrentState() * * @see msi.gama.common.interfaces.IUpdaterTarget#getCurrentState() */ @Override public int getCurrentState() { return IGui.NEUTRAL; } /** * Method resume() * * @see msi.gama.common.interfaces.IUpdaterTarget#resume() */ @Override public void resume() {} public Shell getPopup() { return popup; } // protected LayeredDisplayView getView() { // return view; // } public void display() { if (!isVisible()) { return; } // We first verify that the popup is still ok if (popup.isDisposed()) { return; } update(); relocate(); resize(); if (!popup.isVisible()) { popup.setVisible(true); } } public void relocate() { if (!isVisible()) { return; } if (!popup.isDisposed()) { popup.setLocation(getLocation()); } } public void resize() { if (!isVisible()) { return; } if (!popup.isDisposed()) { final Point size = getSize(); popup.setSize(popup.computeSize(size.x, size.y)); } } public void hide() { if (!popup.isDisposed() && popup.isVisible()) { popup.setSize(0, 0); popup.update(); popup.setVisible(false); } } @Override public boolean isDisposed() { return popup.isDisposed() || viewIsDetached(); } public void close() { if (!popup.isDisposed()) { // Composite c = view.getComponent(); if (referenceComposite != null && !referenceComposite.isDisposed()) { referenceComposite.removeControlListener(listener); } final IPartService ps = ((IWorkbenchPart) view).getSite().getService(IPartService.class); if (ps != null) { ps.removePartListener(pl2); } if (!popup.getParent().isDisposed()) { popup.getParent().removeControlListener(listener); popup.getParent().getShell().removeShellListener(listener); } timer.cancel(); popup.dispose(); } } @Override public boolean isVisible() { // AD: Temporary fix for Issue 548. When a view is detached, the // overlays are not displayed return visible && !isDisposed(); } private boolean viewIsDetached() { // Uses the trick from // http://eclipsesource.com/blogs/2010/06/23/tip-how-to-detect-that-a-view-was-detached/ final boolean[] result = new boolean[] { false }; WorkbenchHelper.run(() -> { final IWorkbenchPartSite site = view.getSite(); if (site == null) { return; } final Shell shell = site.getShell(); if (shell == null) { return; } final String text = shell.getText(); result[0] = text == null || text.isEmpty(); }); return result[0]; } public void setVisible(final boolean visible) { this.visible = visible; if (!visible) { hide(); } else if (!viewIsDetached()) { display(); } } public void dispose() { popup.dispose(); } public double getValueOfOnePixelInModelUnits() { final IDisplaySurface s = view.getDisplaySurface(); if (s == null) { return 1; } final double displayWidth = s.getDisplayWidth(); final double envWidth = s.getEnvWidth(); return envWidth / displayWidth; } public void getOverlayCoordInfo(final StringBuilder sb) { final LayeredDisplayOutput output = view.getOutput(); if (output == null) { return; } final boolean paused = output.isPaused(); final boolean synced = output.getData().isSynchronized(); final IDisplaySurface surface = view.getDisplaySurface(); if (surface != null) { surface.getModelCoordinatesInfo(sb); } if (paused) { sb.append(" | Paused"); } if (synced) { sb.append(" | Synchronized"); } } public void getOverlayZoomInfo(final StringBuilder sb) { final IDisplaySurface surface = view.getDisplaySurface(); if (surface == null) { return; } // if (CORE_SHOW_FPS.getValue()) { sb.append(surface.getFPS()); sb.append(" fps | "); // } int zl = 0; if (view.getOutput() != null) { final Double dataZoom = view.getOutput().getData().getZoomLevel(); if (dataZoom == null) { zl = 1; } else { zl = (int) (dataZoom * 100); } } sb.append("Zoom ").append(zl).append("%"); if (view.isOpenGL()) { final Envelope3D roi = ((IDisplaySurface.OpenGL) surface).getROIDimensions(); if (roi != null) { sb.append(" ROI ["); sb.append(Maths.round(roi.getWidth(), 2)); sb.append(" x "); sb.append(Maths.round(roi.getHeight(), 2)); sb.append("]"); } } } }