package custom.swt.widgets; import org.eclipse.swt.SWT; import org.eclipse.swt.SWTException; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Dialog; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.ProgressBar; import org.eclipse.swt.widgets.ScrollBar; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.Text; import com.github.sergueik.swet.ExceptionDialogEx; import com.github.sergueik.swet.Utils; import custom.swt.widgets.SWTUtil; import custom.swt.widgets.SimpleFuture; import static java.lang.System.err; import static java.lang.System.out; // based on: https://github.com/Holzschneider/Sweater/blob/develop/src/de/dualuse/swt/widgets/ProgressDialog.java public class ProgressDialogEx extends Dialog { final static String CANCEL_LABEL = "Cancel"; final static String PAUSE_LABEL = "Pause"; final static String RESUME_LABEL = "Resume"; final static int DIALOG_WIDTH = 500; final static int LOG_HEIGHT = 400; private final Utils utils = Utils.getInstance(); Shell parent; RuntimeException exception; boolean cancellable = true; boolean pausable = true; Image image; String message; Button cancelButton; Button pauseButton; Button logButton; boolean logOpen = false; boolean done = false; int dialogWidth = DIALOG_WIDTH; public interface Task<E> { void execute(TaskProgress<E> tp); void pause(); void resume(); void cancel(); } public interface TaskProgress<E> { Progress createProgress(); Progress createIndeterminateProgress(); void log(String message); void logWarn(String message); void logError(String message); void abort(); void abort(RuntimeException e); void done(E result); } public interface Progress { Progress setLabel(String label); Progress setValues(int min, int max, int value); Progress setMin(int min); Progress setMax(int max); Progress setValue(int value); Progress indeterminate(); Progress absolute(); void dispose(); } public ProgressDialogEx(Shell parent, String title) { this(parent, SWT.NONE); // default dialog style setText(title); } public ProgressDialogEx(Shell parent, int style) { super(parent, style); this.parent = parent; } public ProgressDialogEx setCancellable(boolean cancellable) { this.cancellable = cancellable; return this; } public ProgressDialogEx setPausable(boolean pausable) { this.pausable = pausable; return this; } public ProgressDialogEx setDescription(Image image, String message) { this.image = image; this.message = message; return this; } public ProgressDialogEx setWidth(int width) { this.dialogWidth = width; return this; } public <E> E open(Task<E> task) { final Shell parent = getParent(); final Display display = parent.getDisplay(); Shell shell = new Shell(parent, SWT.TITLE | SWT.BORDER | SWT.APPLICATION_MODAL); shell.setText(getText()); GridLayout shellLayout = new GridLayout(); shellLayout.marginWidth = 8; shellLayout.marginHeight = 8; shellLayout.horizontalSpacing = 8; shellLayout.numColumns = 1; shell.setLayout(shellLayout); if (image != null || message != null) { ImageLabel label = new ImageLabel(shell, SWT.WRAP); if (image != null) { label.setImage(image); label.setVerticalAlignment(SWT.CENTER); } label.setText(message); GridData labelData = new GridData(SWT.FILL, SWT.FILL, true, false); labelData.widthHint = 492; label.setLayoutData(labelData); } Container progressPane = new Container(shell, SWT.NONE); GridData progressPaneData = new GridData(); progressPaneData.horizontalAlignment = GridData.FILL; progressPaneData.grabExcessHorizontalSpace = true; progressPane.setLayoutData(progressPaneData); GridLayout containerLayout = new GridLayout(); containerLayout.marginWidth = 8; containerLayout.marginHeight = 8; containerLayout.numColumns = 2; progressPane.setLayout(containerLayout); Container buttonPane = new Container(shell, SWT.NONE); GridData buttonPaneData = new GridData(); buttonPaneData.horizontalAlignment = GridData.FILL; buttonPane.setLayoutData(buttonPaneData); GridLayout buttonPaneLayout = new GridLayout(3, false); buttonPane.setLayout(buttonPaneLayout); logButton = new Button(buttonPane, SWT.NONE); Image logButtonImageClosed = new Image(display, utils.getResourceStream("images/right_sm.png")); logButton.setImage(logButtonImageClosed); shell.addListener(SWT.Dispose, (e) -> logButtonImageClosed.dispose()); Image logButtonImageOpen = new Image(display, utils.getResourceStream("images/down_sm.png")); shell.addListener(SWT.Dispose, (e) -> logButtonImageOpen.dispose()); cancelButton = new Button(buttonPane, SWT.NONE); cancelButton.setText(CANCEL_LABEL); cancelButton.setEnabled(cancellable); pauseButton = new Button(buttonPane, SWT.NONE); pauseButton.setText(PAUSE_LABEL); pauseButton.setEnabled(pausable); GridData logButtonData = new GridData(); logButtonData.horizontalAlignment = GridData.BEGINNING; logButtonData.grabExcessHorizontalSpace = true; logButton.setLayoutData(logButtonData); if (cancellable) { cancelButton.addListener(SWT.Selection, (e) -> { if (done) return; task.cancel(); }); shell.addListener(SWT.Dispose, (e) -> task.cancel()); } if (pausable) { pauseButton.addListener(SWT.Selection, new Listener() { boolean paused = false; @Override public void handleEvent(Event event) { if (done) return; if (paused) { paused = false; pauseButton.setText(PAUSE_LABEL); buttonPane.layout(); task.resume(); } else { paused = true; pauseButton.setText(RESUME_LABEL); buttonPane.layout(); task.pause(); } } }); } logButton.addListener(SWT.Selection, new Listener() { @Override public void handleEvent(Event event) { if (logOpen) { logButton.setImage(logButtonImageClosed); logArea.dispose(); logArea = null; shell.layout(); SWTUtil.packHeight(shell); logOpen = false; } else { logArea = new Text(buttonPane, SWT.BORDER | SWT.MULTI | SWT.V_SCROLL | SWT.H_SCROLL) { @Override protected void checkSubclass() { } @Override public Point computeSize(int wHint, int hHint, boolean changed) { Point result = super.computeSize(wHint, hHint, changed); result.y = LOG_HEIGHT; // result.y = Math.min(result.y, LOG_HEIGHT); // grows until max // height, requires re-layout when message is appended return result; } }; GridData textData = new GridData(SWT.FILL, SWT.FILL, false, false, 3, 1); logArea.setLayoutData(textData); ScrollBar bar = logArea.getVerticalBar(); bar.setSelection(bar.getMaximum()); System.out.println( "initial value: " + logArea.getVerticalBar().getSelection() + " / " + logArea.getVerticalBar().getMaximum()); System.out.println("thumb: " + bar.getThumb()); logButton.setImage(logButtonImageOpen); shell.layout(); SWTUtil.packHeight(shell); logOpen = true; } } }); TaskProgressHandler<E> handler = new TaskProgressHandler<E>(shell, progressPane); Thread t = new Thread(new Runnable() { @Override public void run() { task.execute(handler); } }); t.start(); while (!shell.isDisposed()) { if (!display.readAndDispatch()) display.sleep(); } if (handler.result == null && exception != null) throw exception; return handler.result; } Text logArea; StringBuilder logText = new StringBuilder(); class TaskProgressHandler<E> implements TaskProgress<E> { E result; Shell shell; Composite parent; Display display; public TaskProgressHandler(Shell shell, Composite parent) { display = Display.getCurrent(); this.shell = shell; this.parent = parent; } @Override public Progress createProgress() { return createProgressController(SWT.NONE); } @Override public Progress createIndeterminateProgress() { return createProgressController(SWT.INDETERMINATE); } @Override public void log(String message) { display.asyncExec(() -> { logText.append(message); if (logArea != null) { ScrollBar scrollBar = logArea.getVerticalBar(); boolean atBottom = scrollBar.getSelection() + scrollBar.getThumb() >= scrollBar.getMaximum(); System.out.println("atBottom: " + atBottom); System.out.println( scrollBar.getSelection() + " vs " + scrollBar.getMaximum()); System.out.println("thumb: " + scrollBar.getThumb()); int oldSelection = scrollBar.getSelection(); if (!atBottom) { logArea.setRedraw(false); int scrollP = logArea.getTopIndex(); Point selectionP = logArea.getSelection(); logArea.append(message); logArea.setSelection(selectionP); logArea.setTopIndex(scrollP); logArea.setRedraw(true); } else { logArea.append(message); // apparently autoscrolls } // logArea.setText(logText.toString()); // if (atBottom) scrollBar.setSelection(scrollBar.getMaximum()); // if (!atBottom) scrollBar.setSelection(oldSelection); System.out.println( scrollBar.getSelection() + " vs " + scrollBar.getMaximum()); System.out.println("thumb: " + scrollBar.getThumb()); System.out.println(); // ScrollBar bar = logArea.getVerticalBar(); // bar.setSelection(bar.getMaximum()); } }); } @Override public void logWarn(String message) { log(message); } @Override public void logError(String message) { log(message); } @Override public void abort() { display.asyncExec(() -> { result = null; workDone(); }); } @Override public void abort(RuntimeException e) { display.asyncExec(() -> { exception = e; result = null; workDone(); }); } @Override public void done(E res) { display.asyncExec(() -> { result = res; workDone(); }); } private void workDone() { if (logOpen) { cancelButton.setEnabled(false); pauseButton.setEnabled(true); pauseButton.setText("Close"); // pauseButton.requestLayout(); pauseButton.addListener(SWT.Selection, (e) -> shell.dispose()); } else { shell.dispose(); } } private Progress createProgressController(int style) { SimpleFuture<ProgressController> resultFuture = new SimpleFuture<ProgressController>(); display.asyncExec(() -> { resultFuture.put(new ProgressController(parent, style)); // shell.pack(); // implementation of pack: // setSize(computeSize(SWT.DEFAULT, SWT.DEFAULT, true)) Point prefSize = shell.computeSize(dialogWidth, SWT.DEFAULT, true); // DIALOG_WIDTH, // SWT.DEFAULT, // true); shell.setSize(prefSize); SWTUtil.center(shell.getParent(), shell, 0.5, 0.32); if (!shell.isVisible()) shell.open(); }); try { ProgressController result = resultFuture.get(); return result; } catch (InterruptedException e) { e.printStackTrace(); return null; } } } class ProgressController implements Progress { Label progressTitle; Label progressValue; ProgressBar progressBar; Composite parent; Display display; int min = 0, max = 100, current = 0; // default values boolean disposed; public ProgressController(Composite parent) { this(parent, SWT.NONE); } public ProgressController(Composite parent, int style) { this.parent = parent; display = Display.getCurrent(); progressTitle = new Label(parent, SWT.NONE); progressTitle.setText("Progress"); GridData titleData = new GridData(); titleData.horizontalAlignment = GridData.BEGINNING; titleData.grabExcessHorizontalSpace = true; progressTitle.setLayoutData(titleData); progressValue = new Label(parent, SWT.NONE); progressValue.setText("0%"); GridData valueData = new GridData(); valueData.horizontalAlignment = GridData.END; progressValue.setLayoutData(valueData); progressBar = new ProgressBar(parent, style); GridData progressData = new GridData(); progressData.horizontalAlignment = GridData.FILL; progressData.horizontalSpan = 2; progressData.grabExcessHorizontalSpace = true; progressBar.setLayoutData(progressData); } @Override public Progress setLabel(String text) { async(() -> { progressTitle.setText(text); progressTitle.pack(); }); return this; } @Override public Progress setValues(int min, int max, int current) { async(() -> updateValues(min, max, current)); return this; } @Override public Progress setMin(int min) { async(() -> updateMin(min)); return this; } @Override public Progress setMax(int max) { async(() -> updateMax(max)); return this; } @Override public Progress setValue(int current) { async(() -> updateCurrent(current)); return this; } @Override public Progress indeterminate() { async(() -> setIndeterminate(true)); return this; } @Override public Progress absolute() { async(() -> setIndeterminate(false)); return this; } @Override public void dispose() { disposed = true; async(() -> { progressTitle.dispose(); progressBar.dispose(); }); } private void async(Runnable callback) { if (disposed) throw new SWTException(SWT.ERROR_WIDGET_DISPOSED); display.asyncExec(() -> { if (progressTitle.isDisposed() || progressBar.isDisposed()) return; callback.run(); }); } private void updateMin(int min) { progressBar.setMinimum(this.min = min); updateLabel(); } private void updateMax(int max) { progressBar.setMaximum(this.max = max); updateLabel(); } private void updateCurrent(int current) { progressBar.setSelection(this.current = current); setIndeterminate(false); updateLabel(); } private void updateValues(int min, int max, int current) { progressBar.setMinimum(this.min = min); progressBar.setMaximum(this.max = max); progressBar.setSelection(this.current = current); setIndeterminate(false); updateLabel(); } private void setIndeterminate(boolean indeterminate) { if (!indeterminate) { if ((progressBar.getStyle() & SWT.INDETERMINATE) == 0) return; updateStyle(progressBar.getStyle() ^ SWT.INDETERMINATE); // parent.layout(); updateValues(); } else { if ((progressBar.getStyle() & SWT.INDETERMINATE) == SWT.INDETERMINATE) return; updateStyle(progressBar.getStyle() | SWT.INDETERMINATE); // parent.layout(); } } private void updateValues() { updateValues(this.min, this.max, this.current); } private void updateStyle(int newStyle) { // Find old index Composite parent = progressBar.getParent(); Control[] children = parent.getChildren(); int index = -1; for (int i = 0; i < children.length; i++) { if (children[i] == progressBar) { index = i; break; } } if (index == -1) return; // Remove current ProgresBar progressBar.dispose(); // has child already been removed after this call? // Create and position new replacment ProgressBar progressBar = new ProgressBar(parent, newStyle); GridData progressData = new GridData(); progressData.horizontalAlignment = GridData.FILL; progressData.horizontalSpan = 2; progressData.grabExcessHorizontalSpace = true; progressBar.setLayoutData(progressData); // Move to previous position progressBar.moveAbove(parent.getChildren()[index]); // Request layout // progressBar.requestLayout(); } private void updateLabel() { double percentage = current * 100.0 / max; progressValue.setText((int) percentage + " %"); parent.layout(); } } public static abstract class SimpleTask<E> implements Task<E> { public static class CancelledException extends Exception { private static final long serialVersionUID = 1L; } boolean shouldSleep; boolean shouldCancel; Thread thread; @Override public void execute(TaskProgress<E> tp) { thread = Thread.currentThread(); run(tp); } public abstract void run(TaskProgress<E> tp); synchronized protected void yield() { while (shouldSleep && !shouldCancel) { try { wait(); } catch (InterruptedException ex) { } } } synchronized protected void yieldOrCancel() throws CancelledException { while (shouldSleep || shouldCancel) { if (shouldCancel) throw new CancelledException(); try { wait(); } catch (InterruptedException ex) { } } } synchronized protected boolean shouldCancel() { return shouldCancel; } synchronized @Override public void pause() { shouldSleep = true; notifyAll(); } synchronized @Override public void resume() { shouldSleep = false; notifyAll(); } synchronized @Override public void cancel() { shouldCancel = true; notifyAll(); thread.interrupt(); } } private static boolean debug = false; private static final Display display = new Display(); private static final Shell shell = new Shell(display); public static void main(String[] arg) { debug = true; try { // does one need a separate shell a.k.a. hiddenShell ? ProgressDialogEx o = new ProgressDialogEx(shell, "test"); o.setCancellable(true); String result = o.open(new ProgressDialogEx.SimpleTask<String>() { @Override public void run(TaskProgress<String> taskProgress) { try { Progress totalProgress = taskProgress.createProgress(); Progress objectProgress = taskProgress.createProgress(); totalProgress.setLabel("Loading Document..."); objectProgress.setLabel("Loading Object..."); for (int i = 0; i <= 10; i++) { totalProgress.setValue(10 * i); taskProgress.log("Outer loop (" + i + ")\n"); objectProgress.setValues(0, 100, 0); // objectProgress.absolute(); for (int j = 0; j <= 100; j++) { taskProgress.log("\tInner loop (" + j + ")\n"); try { Thread.sleep(64); } catch (InterruptedException e) { } objectProgress.setValue(j); if (j == 50) { objectProgress.indeterminate(); try { Thread.sleep(4000); } catch (InterruptedException e) { } // objectProgress.absolute(); } yieldOrCancel(); } } try { Thread.sleep(1000); } catch (InterruptedException e) { } Progress thirdProgress = taskProgress.createProgress(); for (int i = 0; i <= 100; i++) { taskProgress.log("Extra loop (" + i + ")\n"); try { Thread.sleep(25); } catch (InterruptedException e) { } thirdProgress.setValue(i); yieldOrCancel(); } // taskProgress.abort(new RuntimeException("test")); // taskProgress.abort(); taskProgress.done("Result"); } catch (CancelledException e) { err.println("Cancelled by Exception"); taskProgress.abort(); } } }); err.println(result); } catch (Exception exception) { ExceptionDialogEx.getInstance().render(exception); } } }