]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.ui.workspace.tracker/src/org/simantics/ui/workspace/tracker/internal/contributions/WorkspaceSizeTrackerTrim.java
Added org.simantics.ui.workspace.tracker for tracking workspace size
[simantics/platform.git] / bundles / org.simantics.ui.workspace.tracker / src / org / simantics / ui / workspace / tracker / internal / contributions / WorkspaceSizeTrackerTrim.java
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 (file)
index 0000000..58dca58
--- /dev/null
@@ -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