X-Git-Url: https://gerrit.simantics.org/r/gitweb?p=simantics%2Fplatform.git;a=blobdiff_plain;f=bundles%2Forg.simantics.ui.workspace.tracker%2Fsrc%2Forg%2Fsimantics%2Fui%2Fworkspace%2Ftracker%2Finternal%2Fcontributions%2FWorkspaceSizeTrackerTrim.java;fp=bundles%2Forg.simantics.ui.workspace.tracker%2Fsrc%2Forg%2Fsimantics%2Fui%2Fworkspace%2Ftracker%2Finternal%2Fcontributions%2FWorkspaceSizeTrackerTrim.java;h=58dca581a771aef0480d71b55887cb34049d99b3;hp=0000000000000000000000000000000000000000;hb=ad41b9af52c52b5ea1e9d8c6753f4e341ed40d67;hpb=26313671f3eb6e4d7f41b7a3a2505ca47d26273c diff --git a/bundles/org.simantics.ui.workspace.tracker/src/org/simantics/ui/workspace/tracker/internal/contributions/WorkspaceSizeTrackerTrim.java b/bundles/org.simantics.ui.workspace.tracker/src/org/simantics/ui/workspace/tracker/internal/contributions/WorkspaceSizeTrackerTrim.java new file mode 100644 index 000000000..58dca581a --- /dev/null +++ b/bundles/org.simantics.ui.workspace.tracker/src/org/simantics/ui/workspace/tracker/internal/contributions/WorkspaceSizeTrackerTrim.java @@ -0,0 +1,417 @@ +/******************************************************************************* + * Copyright (c) 2017 Association for Decentralized Information Management in + * Industry THTH ry. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Semantum Oy - initial API and implementation + *******************************************************************************/ +package org.simantics.ui.workspace.tracker.internal.contributions; + +import java.io.IOException; +import java.nio.file.FileStore; +import java.nio.file.Files; +import java.nio.file.Path; + +import org.eclipse.e4.ui.model.application.ui.menu.MToolControl; +import org.eclipse.jface.action.Action; +import org.eclipse.jface.action.IMenuManager; +import org.eclipse.jface.action.MenuManager; +import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.jface.util.IPropertyChangeListener; +import org.eclipse.osgi.util.NLS; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Listener; +import org.eclipse.swt.widgets.Menu; +import org.eclipse.swt.widgets.Shell; +import org.simantics.filesystem.services.sizetracker.SizeTracker; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The Heap Status control, which shows the heap usage statistics in the window trim. + * + * @since 3.1 + */ +public class WorkspaceSizeTrackerTrim extends Composite { + + private Logger logger = LoggerFactory.getLogger(WorkspaceSizeTrackerTrim.class); + + private MToolControl toolControl; + private SizeTracker sizeTracker; + private IPreferenceStore prefStore; + + private Color bgCol, usedSpaceCol, lowSpaceCol, topLeftCol, bottomRightCol, sepCol, textCol; + @SuppressWarnings("unused") + private Color markCol; + + private int updateInterval; + + private String storeName; + private long totalSpace; + private long availableSpace; + private long usedSpace; + private long prevTotalSpace = -1L; + private long prevAvailableSpace = -1L; + private long prevUsedSpace = -1L; + + private boolean hasChanged; + private long mark = -1; + + /** + * How many MB of free disk space means we are low on disk space? + */ + private long lowSpaceThreshold = 500; + private boolean highlightLowSpace = true; + + private boolean updateTooltip = false; + + private final Runnable timer = new Runnable() { + @Override + public void run() { + if (!isDisposed()) { + safeUpdateStats(); + if (hasChanged) { + if (updateTooltip) { + updateToolTip(); + } + redraw(); + hasChanged = false; + } + getDisplay().timerExec(updateInterval, this); + } + } + }; + + private final IPropertyChangeListener prefListener = event -> { + if (IWorkspaceSizeTrackerConstants.PREF_UPDATE_INTERVAL.equals(event.getProperty())) { + setUpdateIntervalInMS(prefStore.getInt(IWorkspaceSizeTrackerConstants.PREF_UPDATE_INTERVAL)); + } else if (IWorkspaceSizeTrackerConstants.PREF_HIGHLIGHT_LOW_SPACE.equals(event.getProperty())) { + highlightLowSpace = prefStore.getBoolean(IWorkspaceSizeTrackerConstants.PREF_HIGHLIGHT_LOW_SPACE); + hasChanged = true; + } else if (IWorkspaceSizeTrackerConstants.PREF_LOW_SPACE_THRESHOLD.equals(event.getProperty())) { + lowSpaceThreshold = prefStore.getLong(IWorkspaceSizeTrackerConstants.PREF_LOW_SPACE_THRESHOLD); + hasChanged = true; + } else if (IWorkspaceSizeTrackerConstants.PREF_SHOW_MONITOR.equals(event.getProperty())) { + boolean show = prefStore.getBoolean(IWorkspaceSizeTrackerConstants.PREF_SHOW_MONITOR); + if (!show) + showTracker(false); + } + }; + + /** + * Creates a new heap status control with the given parent, and using + * the given preference store to obtain settings such as the refresh + * interval. + * @param toolControl + * + * @param parent the parent composite + * @param sizeTracker the workspace sizeTracker service + * @param prefStore the preference store + */ + public WorkspaceSizeTrackerTrim(Composite parent, MToolControl toolControl, SizeTracker sizeTracker, IPreferenceStore prefStore) { + super(parent, SWT.NONE); + this.toolControl = toolControl; + this.sizeTracker = sizeTracker; + + this.prefStore = prefStore; + prefStore.addPropertyChangeListener(prefListener); + + setUpdateIntervalInMS(prefStore.getInt(IWorkspaceSizeTrackerConstants.PREF_UPDATE_INTERVAL)); + + Display display = getDisplay(); + usedSpaceCol = display.getSystemColor(SWT.COLOR_WIDGET_NORMAL_SHADOW); + lowSpaceCol = new Color(display, 255, 70, 70); // medium red + bgCol = display.getSystemColor(SWT.COLOR_WIDGET_BACKGROUND); + sepCol = topLeftCol = display.getSystemColor(SWT.COLOR_WIDGET_NORMAL_SHADOW); + bottomRightCol = display.getSystemColor(SWT.COLOR_WIDGET_HIGHLIGHT_SHADOW); + markCol = textCol = display.getSystemColor(SWT.COLOR_WIDGET_FOREGROUND); + + createContextMenu(); + + Listener listener = event -> { + switch (event.type) { + case SWT.Dispose: + doDispose(); + break; + case SWT.Paint: + if (event.widget == WorkspaceSizeTrackerTrim.this) { + paintComposite(event.gc); + } + break; + case SWT.MouseDown: + if (event.button == 1) { + if (event.widget == WorkspaceSizeTrackerTrim.this) { + setMark(); + } + } + break; + case SWT.MouseEnter: + WorkspaceSizeTrackerTrim.this.updateTooltip = true; + updateToolTip(); + break; + case SWT.MouseExit: + if (event.widget == WorkspaceSizeTrackerTrim.this) { + WorkspaceSizeTrackerTrim.this.updateTooltip = false; + } + break; + } + }; + addListener(SWT.Dispose, listener); + addListener(SWT.MouseDown, listener); + addListener(SWT.Paint, listener); + addListener(SWT.MouseEnter, listener); + addListener(SWT.MouseExit, listener); + + // make sure stats are updated before first paint + safeUpdateStats(); + + getDisplay().asyncExec(() -> { + if (!isDisposed()) { + getDisplay().timerExec(updateInterval, timer); + } + }); + } + + @Override + public void setBackground(Color color) { + bgCol = color; + } + + @Override + public void setForeground(Color color) { + if (color == null) { + markCol = textCol = getDisplay().getSystemColor(SWT.COLOR_INFO_BACKGROUND); + } else { + markCol = textCol = color; + } + } + + @Override + public Color getForeground() { + if (usedSpaceCol != null) { + return usedSpaceCol; + } + return getDisplay().getSystemColor(SWT.COLOR_INFO_BACKGROUND); + } + + private void setUpdateIntervalInMS(int interval) { + updateInterval = Math.max(100, interval); + } + + private void doDispose() { + prefStore.removePropertyChangeListener(prefListener); + if (lowSpaceCol != null) { + lowSpaceCol.dispose(); + } + } + + @Override + public Point computeSize(int wHint, int hHint, boolean changed) { + GC gc = new GC(this); + Point p = gc.textExtent(Messages.WorkspaceSizeTrackerTrim_widthStr); + int height = p.y + 4; + gc.dispose(); + return new Point(p.x, height); + } + + /** + * Creates the context menu + */ + private void createContextMenu() { + MenuManager menuMgr = new MenuManager(); + menuMgr.setRemoveAllWhenShown(true); + menuMgr.addMenuListener(menuMgr1 -> fillMenu(menuMgr1)); + Menu menu = menuMgr.createContextMenu(this); + setMenu(menu); + } + + private void fillMenu(IMenuManager menuMgr) { + menuMgr.add(new SetMarkAction()); + menuMgr.add(new ClearMarkAction()); + menuMgr.add(new CloseSizeTrackerTrimAction()); + } + + /** + * Sets the mark to the current usedMem level. + */ + private void setMark() { + safeUpdateStats(); // get up-to-date stats before taking the mark + mark = usedSpace; + hasChanged = true; + redraw(); + } + + /** + * Clears the mark. + */ + private void clearMark() { + mark = -1; + hasChanged = true; + redraw(); + } + + private void paintComposite(GC gc) { + paintCompositeMaxUnknown(gc); + } + + private void paintCompositeMaxUnknown(GC gc) { + Rectangle rect = getClientArea(); + int x = rect.x; + int y = rect.y; + int w = rect.width; + int h = rect.height; + int sw = w - 1; // status width + long storeUsedSpace = totalSpace - availableSpace; + int uw = (int) (sw * storeUsedSpace / totalSpace); // used space width + int ux = x + 1 + uw; // used space right edge + if (bgCol != null) { + gc.setBackground(bgCol); + } + gc.fillRectangle(rect); + + boolean lowOnSpace = false; + if (highlightLowSpace) { + lowOnSpace = (1024L*1024L*lowSpaceThreshold) >= availableSpace; + } + + gc.setForeground(sepCol); + gc.drawLine(ux, y, ux, y + h); + gc.setForeground(topLeftCol); + gc.drawLine(x, y, x+w, y); + gc.drawLine(x, y, x, y+h); + gc.setForeground(bottomRightCol); + gc.drawLine(x+w-1, y, x+w-1, y+h); + gc.drawLine(x, y+h-1, x+w, y+h-1); + + gc.setBackground(lowOnSpace ? lowSpaceCol : usedSpaceCol); + gc.fillRectangle(x + 1, y + 1, uw, h - 2); + + String s = NLS.bind(Messages.WorkspaceSizeTrackerTrim_status, convertToSizeString(usedSpace), convertToSizeString(availableSpace)); + Point p = gc.textExtent(s); + int sx = (rect.width - 15 - p.x) / 2 + rect.x + 1; + int sy = (rect.height - 2 - p.y) / 2 + rect.y + 1; + gc.setForeground(textCol); + gc.drawString(s, sx, sy, true); + } + + private void safeUpdateStats() { + try { + updateStats(); + } catch (IOException e) { + logger.error("Failed to update workspace size statistics.", e); + } + } + + private void updateStats() throws IOException { + Path path = sizeTracker.path(); + FileStore store = Files.getFileStore(path); + + storeName = store.toString(); + totalSpace = store.getTotalSpace(); + availableSpace = store.getUsableSpace(); + usedSpace = sizeTracker.size(); + + if (convertToMeg(prevTotalSpace) != convertToMeg(totalSpace)) { + prevTotalSpace = totalSpace; + this.hasChanged = true; + } + if (prevAvailableSpace != availableSpace) { + prevAvailableSpace = availableSpace; + this.hasChanged = true; + } + if (convertToMeg(prevUsedSpace) != convertToMeg(usedSpace)) { + prevUsedSpace = usedSpace; + this.hasChanged = true; + } + } + + private void updateToolTip() { + String usedStr = convertToSizeString(usedSpace); + String availableStr = convertToSizeString(availableSpace); + String totalStr = convertToSizeString(totalSpace); + String markStr = mark == -1 ? Messages.WorkspaceSizeTrackerTrim_noMark : convertToSizeString(mark); + String toolTip = NLS.bind(Messages.WorkspaceSizeTrackerTrim_memoryToolTip, new Object[] { usedStr, storeName, availableStr, totalStr, markStr }); + if (!toolTip.equals(getToolTipText())) { + setToolTipText(toolTip); + } + } + + /** + * Converts the given number of bytes to a printable number of megabytes (rounded up). + */ + private String convertToSizeString(long numBytes) { + long megs = convertToMeg(numBytes); + if (megs > 10000) { + double megsd = (double) megs; + long gigs = (long) Math.floor(megsd / 1024.0); + long decimals = (long) (megsd - gigs*1024); + decimals = (decimals + 5) / 10; + return NLS.bind(Messages.WorkspaceSizeTrackerTrim_gig, new Long(gigs), new Long(decimals)); + } else { + return NLS.bind(Messages.WorkspaceSizeTrackerTrim_meg, new Long(megs)); + } + } + + /** + * Converts the given number of bytes to the corresponding number of megabytes (rounded up). + */ + private long convertToMeg(long numBytes) { + return (numBytes + (512 * 1024)) / (1024 * 1024); + } + + class SetMarkAction extends Action { + SetMarkAction() { + super(Messages.SetMarkAction_text); + } + + @Override + public void run() { + setMark(); + } + } + + class ClearMarkAction extends Action { + ClearMarkAction() { + super(Messages.ClearMarkAction_text); + } + + @Override + public void run() { + clearMark(); + } + } + + class CloseSizeTrackerTrimAction extends Action{ + CloseSizeTrackerTrimAction(){ + super(Messages.WorkspaceSizeTrackerTrim_close); + } + + @Override + public void run(){ + showTracker(false); + } + } + + private void showTracker(boolean show) { + if (toolControl.isToBeRendered() != show) { + Object widget = toolControl.getWidget(); + Shell shell = widget instanceof Control ? ((Control) widget).getShell() : null; + toolControl.setToBeRendered(show); + if (shell != null) + shell.layout(null, SWT.ALL | SWT.CHANGED | SWT.DEFER); + prefStore.setValue(IWorkspaceSizeTrackerConstants.PREF_SHOW_MONITOR, show); + } + } + +} \ No newline at end of file