--- /dev/null
+/*******************************************************************************
+ * 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