Rewrite delayed text
[gpl/argeo-suite.git] / org.argeo.app.ui / src / org / argeo / app / ui / widgets / DelayedText.java
index 1eab6d6a9c359a92a9a5a33b6c4683069cf4541f..ecf66396868364ba893c187a5dbf0014babe04bd 100644 (file)
 package org.argeo.app.ui.widgets;
 
-import java.util.Timer;
-import java.util.TimerTask;
+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.Event;
 import org.eclipse.swt.widgets.Text;
 
 /**
- * Text that introduce a timer in the attached ModifyListener.
- * 
- * Note that corresponding ModifyEvent will *NOT* be sent in the UI thread.
- * Calling ModifierInstance must be implemented in consequence. Note also that
- * this delayed text only manages one listener at a time.
- *
+ * A text input which notifies changes after a delay, typically in order to
+ * apply a filter.
  */
 public class DelayedText {
-       final int delay;
-       private Object lock = new Object();
-       private MyTimer timer = new MyTimer(DelayedText.this.toString());
-       private ModifyListener delayedModifyListener;
-       private ServerPushSession pushSession;
-
-       private Text text;
-
-       private ModifyListener modifyListener = new ModifyListener() {
-               private static final long serialVersionUID = 1117506414462641980L;
+       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;
 
-               public void modifyText(ModifyEvent e) {
-                       ModifyEvent delayedEvent = null;
-                       synchronized (lock) {
-                               if (delayedModifyListener != null) {
-                                       Event tmpEvent = new Event();
-                                       tmpEvent.widget = text;
-                                       tmpEvent.display = e.display;
-                                       tmpEvent.data = e.data;
-                                       tmpEvent.time = e.time;
-                                       delayedEvent = new ModifyEvent(tmpEvent);
-                               }
-                       }
-                       final ModifyEvent timerModifyEvent = delayedEvent;
+       private final long delay;
+       private final InternalModifyListener modifyListener;
+       private final Text text;
+       protected List<Consumer<String>> toDos = new ArrayList<>();
+       private ServerPushSession pushSession;
 
-                       synchronized (timer) {
-                               if (timer.timerTask != null) {
-                                       timer.timerTask.cancel();
-                                       timer.timerTask = null;
-                               }
+       private ScheduledFuture<String> lastTask;
 
-                               if (delayedEvent != null) {
-                                       timer.timerTask = new TimerTask() {
-                                               public void run() {
-                                                       synchronized (lock) {
-                                                               delayedModifyListener.modifyText(timerModifyEvent);
-                                                       }
-                                                       synchronized (timer) {
-                                                               timer.timerTask = null;
-                                                       }
-                                               }
-                                       };
-                                       timer.schedule(timer.timerTask, delay);
-                                       if (pushSession != null)
-                                               pushSession.start();
-                               }
-                       }
-               };
-       };
+       public DelayedText(Composite parent, int style) {
+               this(parent, style, DEFAULT_DELAY);
+       }
 
-       public DelayedText(Composite parent, int style, int delayInMs) {
-               // super(parent, style);
-               text = new Text(parent, style);
+       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);
        }
 
-       /**
-        * Adds a modify text listener that will be delayed. If another Modify event
-        * happens during the waiting delay, the older event will be cancelled an a new
-        * one will be scheduled after another new delay.
-        */
-       public void addDelayedModifyListener(ServerPushSession pushSession, ModifyListener listener) {
-               synchronized (lock) {
-                       delayedModifyListener = listener;
-                       this.pushSession = pushSession;
+       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 void removeDelayedModifyListener(ModifyListener listener) {
-               synchronized (lock) {
-                       delayedModifyListener = null;
-                       pushSession = null;
-               }
+       public Text getText() {
+               return text;
        }
 
-       private class MyTimer extends Timer {
-               private TimerTask timerTask = null;
-
-               public MyTimer(String name) {
-                       super(name);
-               }
+       public void addListener(Consumer<String> toDo) {
+               toDos.add(toDo);
        }
 
-       public Text getText() {
-               return text;
-       }
+       private class InternalModifyListener implements ModifyListener {
+               private static final long serialVersionUID = -6178431173400385005L;
 
-       public void close() {
-               if (pushSession != null)
-                       pushSession.stop();
-               if (timer != null)
-                       timer.cancel();
+               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;
+               }
        };
 
 }