]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.ui.workspace.tracker/src/org/simantics/ui/workspace/tracker/internal/contributions/WorkspaceSizeTrackerTrim.java
Fixed multiple issues causing dangling references to discarded queries
[simantics/platform.git] / bundles / org.simantics.ui.workspace.tracker / src / org / simantics / ui / workspace / tracker / internal / contributions / WorkspaceSizeTrackerTrim.java
1 /*******************************************************************************
2  * Copyright (c) 2017 Association for Decentralized Information Management in
3  * Industry THTH ry.
4  * All rights reserved. This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License v1.0
6  * which accompanies this distribution, and is available at
7  * http://www.eclipse.org/legal/epl-v10.html
8  *
9  * Contributors:
10  *     Semantum Oy - initial API and implementation
11  *******************************************************************************/
12 package org.simantics.ui.workspace.tracker.internal.contributions;
13
14 import java.io.IOException;
15 import java.nio.file.FileStore;
16 import java.nio.file.Files;
17 import java.nio.file.Path;
18
19 import org.eclipse.e4.ui.model.application.ui.menu.MToolControl;
20 import org.eclipse.jface.action.Action;
21 import org.eclipse.jface.action.IMenuManager;
22 import org.eclipse.jface.action.MenuManager;
23 import org.eclipse.jface.preference.IPreferenceStore;
24 import org.eclipse.jface.util.IPropertyChangeListener;
25 import org.eclipse.osgi.util.NLS;
26 import org.eclipse.swt.SWT;
27 import org.eclipse.swt.graphics.Color;
28 import org.eclipse.swt.graphics.GC;
29 import org.eclipse.swt.graphics.Point;
30 import org.eclipse.swt.graphics.Rectangle;
31 import org.eclipse.swt.widgets.Composite;
32 import org.eclipse.swt.widgets.Control;
33 import org.eclipse.swt.widgets.Display;
34 import org.eclipse.swt.widgets.Listener;
35 import org.eclipse.swt.widgets.Menu;
36 import org.eclipse.swt.widgets.Shell;
37 import org.simantics.filesystem.services.sizetracker.SizeTracker;
38 import org.simantics.ui.workspace.tracker.IWorkspaceSizeTrackerConstants;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
41
42 /**
43  * The Heap Status control, which shows the heap usage statistics in the window trim.
44  *
45  * @since 3.1
46  */
47 public class WorkspaceSizeTrackerTrim extends Composite {
48
49         private Logger logger = LoggerFactory.getLogger(WorkspaceSizeTrackerTrim.class);
50
51         private MToolControl toolControl;
52         private SizeTracker sizeTracker;
53         private IPreferenceStore prefStore;
54
55         /**
56          * How many MB of free disk space means we are low on disk space? 
57          */
58         private long lowSpaceThreshold = IWorkspaceSizeTrackerConstants.DEFAULT_LOW_SPACE_THRESHOLD;
59         private boolean highlightLowSpace = IWorkspaceSizeTrackerConstants.DEFAULT_HIGHLIGHT_LOW_SPACE;
60         private int updateInterval = IWorkspaceSizeTrackerConstants.DEFAULT_UPDATE_INTERVAL;
61
62         private Color bgCol, usedSpaceCol, lowSpaceCol, topLeftCol, bottomRightCol, sepCol, textCol;
63         @SuppressWarnings("unused")
64         private Color markCol;
65
66         private String storeName;
67         private long totalSpace;
68         private long availableSpace;
69         private long usedSpace;
70         private long prevTotalSpace = -1L;
71         private long prevAvailableSpace = -1L;
72         private long prevUsedSpace = -1L;
73
74         private boolean hasChanged;
75         private long mark = -1;
76
77         private boolean updateTooltip = false;
78
79         private final Runnable timer = new Runnable() {
80                 @Override
81                 public void run() {
82                         if (!isDisposed()) {
83                                 safeUpdateStats();
84                                 if (hasChanged) {
85                                         if (updateTooltip) {
86                                                 updateToolTip();
87                                         }
88                                         redraw();
89                                         hasChanged = false;
90                                 }
91                                 getDisplay().timerExec(updateInterval, this);
92                         }
93                 }
94         };
95
96         private final IPropertyChangeListener prefListener = event -> {
97                 if (IWorkspaceSizeTrackerConstants.PREF_UPDATE_INTERVAL.equals(event.getProperty())) {
98                         setUpdateIntervalInMS(prefStore.getInt(IWorkspaceSizeTrackerConstants.PREF_UPDATE_INTERVAL));
99                 } else if (IWorkspaceSizeTrackerConstants.PREF_HIGHLIGHT_LOW_SPACE.equals(event.getProperty())) {
100                         highlightLowSpace = prefStore.getBoolean(IWorkspaceSizeTrackerConstants.PREF_HIGHLIGHT_LOW_SPACE);
101                         hasChanged = true;
102                 } else if (IWorkspaceSizeTrackerConstants.PREF_LOW_SPACE_THRESHOLD.equals(event.getProperty())) {
103                         lowSpaceThreshold = prefStore.getLong(IWorkspaceSizeTrackerConstants.PREF_LOW_SPACE_THRESHOLD);
104                         hasChanged = true;
105                 } else if (IWorkspaceSizeTrackerConstants.PREF_SHOW_MONITOR.equals(event.getProperty())) {
106                         boolean show = prefStore.getBoolean(IWorkspaceSizeTrackerConstants.PREF_SHOW_MONITOR);
107                         if (!show)
108                                 showTracker(false);
109                 }
110         };
111
112         /**
113          * Creates a new heap status control with the given parent, and using
114          * the given preference store to obtain settings such as the refresh
115          * interval.
116          * @param toolControl 
117          *
118          * @param parent the parent composite
119          * @param sizeTracker the workspace sizeTracker service 
120          * @param prefStore the preference store
121          */
122         public WorkspaceSizeTrackerTrim(Composite parent, MToolControl toolControl, SizeTracker sizeTracker, IPreferenceStore prefStore) {
123                 super(parent, SWT.NONE);
124                 this.toolControl = toolControl;
125                 this.sizeTracker = sizeTracker;
126
127                 this.prefStore = prefStore;
128                 prefStore.addPropertyChangeListener(prefListener);
129
130                 setUpdateIntervalInMS(prefStore.getInt(IWorkspaceSizeTrackerConstants.PREF_UPDATE_INTERVAL));
131                 highlightLowSpace = prefStore.getBoolean(IWorkspaceSizeTrackerConstants.PREF_HIGHLIGHT_LOW_SPACE);
132                 lowSpaceThreshold = prefStore.getLong(IWorkspaceSizeTrackerConstants.PREF_LOW_SPACE_THRESHOLD);
133
134                 Display display = getDisplay();
135                 usedSpaceCol = display.getSystemColor(SWT.COLOR_WIDGET_NORMAL_SHADOW);
136                 lowSpaceCol = new Color(display, 255, 70, 70);  // medium red
137                 bgCol = display.getSystemColor(SWT.COLOR_WIDGET_BACKGROUND);
138                 sepCol = topLeftCol = display.getSystemColor(SWT.COLOR_WIDGET_NORMAL_SHADOW);
139                 bottomRightCol = display.getSystemColor(SWT.COLOR_WIDGET_HIGHLIGHT_SHADOW);
140                 markCol = textCol = display.getSystemColor(SWT.COLOR_WIDGET_FOREGROUND);
141
142                 createContextMenu();
143
144                 Listener listener = event -> {
145                         switch (event.type) {
146                         case SWT.Dispose:
147                                 doDispose();
148                                 break;
149                         case SWT.Paint:
150                                 if (event.widget == WorkspaceSizeTrackerTrim.this) {
151                                         paintComposite(event.gc);
152                                 }
153                                 break;
154                         case SWT.MouseDown:
155                                 if (event.button == 1) {
156                                         if (event.widget == WorkspaceSizeTrackerTrim.this) {
157                                                 setMark();
158                                         }
159                                 }
160                                 break;
161                         case SWT.MouseEnter:
162                                 WorkspaceSizeTrackerTrim.this.updateTooltip = true;
163                                 updateToolTip();
164                                 break;
165                         case SWT.MouseExit:
166                                 if (event.widget == WorkspaceSizeTrackerTrim.this) {
167                                         WorkspaceSizeTrackerTrim.this.updateTooltip = false;
168                                 }
169                                 break;
170                         }
171                 };
172                 addListener(SWT.Dispose, listener);
173                 addListener(SWT.MouseDown, listener);
174                 addListener(SWT.Paint, listener);
175                 addListener(SWT.MouseEnter, listener);
176                 addListener(SWT.MouseExit, listener);
177
178                 // make sure stats are updated before first paint
179                 safeUpdateStats();
180
181                 getDisplay().asyncExec(() -> {
182                         if (!isDisposed()) {
183                                 getDisplay().timerExec(updateInterval, timer);
184                         }
185                 });
186         }
187
188         @Override
189         public void setBackground(Color color) {
190                 bgCol = color;
191         }
192
193         @Override
194         public void setForeground(Color color) {
195                 if (color == null) {
196                         markCol = textCol = getDisplay().getSystemColor(SWT.COLOR_INFO_BACKGROUND);
197                 } else {
198                         markCol = textCol = color;
199                 }
200         }
201
202         @Override
203         public Color getForeground() {
204                 if (usedSpaceCol != null) {
205                         return usedSpaceCol;
206                 }
207                 return getDisplay().getSystemColor(SWT.COLOR_INFO_BACKGROUND);
208         }
209
210         private void setUpdateIntervalInMS(int interval) {
211                 updateInterval = Math.max(100, interval);
212         }
213
214         private void doDispose() {
215                 prefStore.removePropertyChangeListener(prefListener);
216                 if (lowSpaceCol != null) {
217                         lowSpaceCol.dispose();
218                 }
219         }
220
221         @Override
222         public Point computeSize(int wHint, int hHint, boolean changed) {
223                 GC gc = new GC(this);
224                 Point p = gc.textExtent(Messages.WorkspaceSizeTrackerTrim_widthStr);
225                 int height = p.y + 4;
226                 gc.dispose();
227                 return new Point(p.x, height);
228         }
229
230         /**
231          * Creates the context menu
232          */
233         private void createContextMenu() {
234                 MenuManager menuMgr = new MenuManager();
235                 menuMgr.setRemoveAllWhenShown(true);
236                 menuMgr.addMenuListener(menuMgr1 -> fillMenu(menuMgr1));
237                 Menu menu = menuMgr.createContextMenu(this);
238                 setMenu(menu);
239         }
240
241         private void fillMenu(IMenuManager menuMgr) {
242                 menuMgr.add(new SetMarkAction());
243                 menuMgr.add(new ClearMarkAction());
244                 menuMgr.add(new CloseSizeTrackerTrimAction());
245         }
246
247         /**
248          * Sets the mark to the current usedMem level.
249          */
250         private void setMark() {
251                 safeUpdateStats();  // get up-to-date stats before taking the mark
252                 mark = usedSpace;
253                 hasChanged = true;
254                 redraw();
255         }
256
257         /**
258          * Clears the mark.
259          */
260         private void clearMark() {
261                 mark = -1;
262                 hasChanged = true;
263                 redraw();
264         }
265
266         private void paintComposite(GC gc) {
267                 paintCompositeMaxUnknown(gc);
268         }
269
270         private void paintCompositeMaxUnknown(GC gc) {
271                 Rectangle rect = getClientArea();
272                 int x = rect.x;
273                 int y = rect.y;
274                 int w = rect.width;
275                 int h = rect.height;
276                 int sw = w - 1; // status width
277                 long storeUsedSpace = totalSpace - availableSpace;
278                 int uw = (int) (sw * storeUsedSpace / totalSpace); // used space width
279                 int ux = x + 1 + uw; // used space right edge
280                 if (bgCol != null) {
281                         gc.setBackground(bgCol);
282                 }
283                 gc.fillRectangle(rect);
284
285                 boolean lowOnSpace = false;
286                 if (highlightLowSpace) {
287                         lowOnSpace = (1024L*1024L*lowSpaceThreshold) >= availableSpace;
288                 }
289
290                 gc.setForeground(sepCol);
291                 gc.drawLine(ux, y, ux, y + h);
292                 gc.setForeground(topLeftCol);
293                 gc.drawLine(x, y, x+w, y);
294                 gc.drawLine(x, y, x, y+h);
295                 gc.setForeground(bottomRightCol);
296                 gc.drawLine(x+w-1, y, x+w-1, y+h);
297                 gc.drawLine(x, y+h-1, x+w, y+h-1);
298
299                 gc.setBackground(lowOnSpace ? lowSpaceCol : usedSpaceCol);
300                 gc.fillRectangle(x + 1, y + 1, uw, h - 2);
301
302                 String s = NLS.bind(Messages.WorkspaceSizeTrackerTrim_status, convertToSizeString(usedSpace), convertToSizeString(availableSpace));
303                 Point p = gc.textExtent(s);
304                 int sx = (rect.width - 15 - p.x) / 2 + rect.x + 1;
305                 int sy = (rect.height - 2 - p.y) / 2 + rect.y + 1;
306                 gc.setForeground(textCol);
307                 gc.drawString(s, sx, sy, true);
308         }
309
310         private void safeUpdateStats()  {
311                 try {
312                         updateStats();
313                 } catch (IOException e) {
314                         logger.error("Failed to update workspace size statistics.", e);
315                 }
316         }
317
318         private void updateStats() throws IOException {
319                 Path path = sizeTracker.path();
320                 FileStore store = Files.getFileStore(path);
321
322                 storeName = store.toString();
323                 totalSpace = store.getTotalSpace();
324                 availableSpace = store.getUsableSpace();
325                 usedSpace = sizeTracker.size();
326
327                 if (convertToMeg(prevTotalSpace) != convertToMeg(totalSpace)) {
328                         prevTotalSpace = totalSpace;
329                         this.hasChanged = true;
330                 }
331                 if (prevAvailableSpace != availableSpace) {
332                         prevAvailableSpace = availableSpace;
333                         this.hasChanged = true;
334                 }
335                 if (convertToMeg(prevUsedSpace) != convertToMeg(usedSpace)) {
336                         prevUsedSpace = usedSpace;
337                         this.hasChanged = true;
338                 }
339         }
340
341         private void updateToolTip() {
342                 String usedStr = convertToSizeString(usedSpace);
343                 String availableStr = convertToSizeString(availableSpace);
344                 String totalStr = convertToSizeString(totalSpace);
345                 String markStr = mark == -1 ? Messages.WorkspaceSizeTrackerTrim_noMark : convertToSizeString(mark);
346                 String toolTip = NLS.bind(Messages.WorkspaceSizeTrackerTrim_memoryToolTip, new Object[] { usedStr, storeName, availableStr, totalStr, markStr });
347                 if (!toolTip.equals(getToolTipText())) {
348                         setToolTipText(toolTip);
349                 }
350         }
351
352         /**
353          * Converts the given number of bytes to a printable number of megabytes (rounded up).
354          */
355         private String convertToSizeString(long numBytes) {
356                 long megs = convertToMeg(numBytes);
357                 if (megs > 10000) {
358                         double megsd = (double) megs;
359                         long gigs = (long) Math.floor(megsd / 1024.0);
360                         long decimals = (long) (megsd - gigs*1024);
361                         decimals = (decimals + 5) / 10;
362                         return NLS.bind(Messages.WorkspaceSizeTrackerTrim_gig, new Long(gigs), new Long(decimals));
363                 } else {
364                         return NLS.bind(Messages.WorkspaceSizeTrackerTrim_meg, new Long(megs));
365                 }
366         }
367
368         /**
369          * Converts the given number of bytes to the corresponding number of megabytes (rounded up).
370          */
371         private long convertToMeg(long numBytes) {
372                 return (numBytes + (512 * 1024)) / (1024 * 1024);
373         }
374
375         class SetMarkAction extends Action {
376                 SetMarkAction() {
377                         super(Messages.SetMarkAction_text);
378                 }
379
380                 @Override
381                 public void run() {
382                         setMark();
383                 }
384         }
385
386         class ClearMarkAction extends Action {
387                 ClearMarkAction() {
388                         super(Messages.ClearMarkAction_text);
389                 }
390
391                 @Override
392                 public void run() {
393                         clearMark();
394                 }
395         }
396
397         class CloseSizeTrackerTrimAction extends Action{
398                 CloseSizeTrackerTrimAction(){
399                         super(Messages.WorkspaceSizeTrackerTrim_close);
400                 }
401
402                 @Override
403                 public void run(){
404                         showTracker(false);
405                 }
406         }
407
408         private void showTracker(boolean show) {
409                 if (toolControl.isToBeRendered() != show) {
410                         Object widget = toolControl.getWidget();
411                         Shell shell = widget instanceof Control ? ((Control) widget).getShell() : null;
412                         toolControl.setToBeRendered(show);
413                         if (shell != null)
414                                 shell.layout(null, SWT.ALL | SWT.CHANGED | SWT.DEFER);
415                         prefStore.setValue(IWorkspaceSizeTrackerConstants.PREF_SHOW_MONITOR, show);
416                 }
417         }
418
419 }