Merge remote-tracking branch 'origin/unstable' into testing
[gpl/argeo-suite.git] / org.argeo.app.ui / src / org / argeo / app / ui / widgets / DelayedText.java
diff --git a/org.argeo.app.ui/src/org/argeo/app/ui/widgets/DelayedText.java b/org.argeo.app.ui/src/org/argeo/app/ui/widgets/DelayedText.java
new file mode 100644 (file)
index 0000000..ecf6639
--- /dev/null
@@ -0,0 +1,91 @@
+package org.argeo.app.ui.widgets;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+
+import org.eclipse.rap.rwt.service.ServerPushSession;
+import org.eclipse.swt.events.ModifyEvent;
+import org.eclipse.swt.events.ModifyListener;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Text;
+
+/**
+ * A text input which notifies changes after a delay, typically in order to
+ * apply a filter.
+ */
+public class DelayedText {
+       private final static ScheduledExecutorService scheduler;
+       static {
+               // create only one scheduler, in order not to exhaust threads
+               scheduler = Executors.newScheduledThreadPool(0, (r) -> {
+                       Thread thread = new Thread(r, "Delayed text scheduler");
+                       // we mark threads as deamons so that the shutdown hook is triggered
+                       thread.setDaemon(true);
+                       return thread;
+               });
+               Runtime.getRuntime().addShutdownHook(new Thread(() -> {
+                       scheduler.shutdown();
+               }, "Shutdown delayed text scheduler"));
+       }
+       private final static int DEFAULT_DELAY = 800;
+
+       private final long delay;
+       private final InternalModifyListener modifyListener;
+       private final Text text;
+       protected List<Consumer<String>> toDos = new ArrayList<>();
+       private ServerPushSession pushSession;
+
+       private ScheduledFuture<String> lastTask;
+
+       public DelayedText(Composite parent, int style) {
+               this(parent, style, DEFAULT_DELAY);
+       }
+
+       public DelayedText(Composite parent, int style, long delayInMs) {
+               this.delay = delayInMs;
+               this.modifyListener = new InternalModifyListener();
+               pushSession = new ServerPushSession();
+               pushSession.start();
+               text = new Text(parent, style);
+               text.addModifyListener(modifyListener);
+       }
+
+       protected void notifyText(String txt) {
+               // text.getDisplay().syncExec(()-> pushSession.start());
+               for (Consumer<String> toDo : toDos) {
+                       text.getDisplay().syncExec(() -> toDo.accept(txt));
+               }
+               // text.getDisplay().syncExec(()->pushSession.stop());
+       }
+
+       public Text getText() {
+               return text;
+       }
+
+       public void addListener(Consumer<String> toDo) {
+               toDos.add(toDo);
+       }
+
+       private class InternalModifyListener implements ModifyListener {
+               private static final long serialVersionUID = -6178431173400385005L;
+
+               public void modifyText(ModifyEvent e) {
+                       String txt = text.getText();
+                       ScheduledFuture<String> task = scheduler.schedule(() -> {
+                               notifyText(txt);
+                               return txt;
+                       }, delay, TimeUnit.MILLISECONDS);
+                       // cancel previous task
+                       if (lastTask != null && !lastTask.isDone()) {
+                               lastTask.cancel(false);
+                       }
+                       lastTask = task;
+               }
+       };
+
+}