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);
- // Bad approach: it is not a good idea to put a
- // display.asyncExec in a lock...
- // DelayedText.this.getDisplay().asyncExec(new
- // Runnable() {
- // @Override
- // public void run() {
- // 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 canceled 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;
+ }
};
}