+ * Public constructor. The backend type will be BackendType.ECMA48.
+ *
+ * @param input an InputStream connected to the remote user, or null for
+ * System.in. If System.in is used, then on non-Windows systems it will
+ * be put in raw mode; shutdown() will (blindly!) put System.in in cooked
+ * mode. input is always converted to a Reader with UTF-8 encoding.
+ * @param output an OutputStream connected to the remote user, or null
+ * for System.out. output is always converted to a Writer with UTF-8
+ * encoding.
+ * @throws UnsupportedEncodingException if an exception is thrown when
+ * creating the InputStreamReader
+ */
+ public TApplication(final InputStream input,
+ final OutputStream output) throws UnsupportedEncodingException {
+
+ backend = new ECMA48Backend(this, input, output);
+ TApplicationImpl();
+ }
+
+ /**
+ * Public constructor. The backend type will be BackendType.ECMA48.
+ *
+ * @param input the InputStream underlying 'reader'. Its available()
+ * method is used to determine if reader.read() will block or not.
+ * @param reader a Reader connected to the remote user.
+ * @param writer a PrintWriter connected to the remote user.
+ * @param setRawMode if true, set System.in into raw mode with stty.
+ * This should in general not be used. It is here solely for Demo3,
+ * which uses System.in.
+ * @throws IllegalArgumentException if input, reader, or writer are null.
+ */
+ public TApplication(final InputStream input, final Reader reader,
+ final PrintWriter writer, final boolean setRawMode) {
+
+ backend = new ECMA48Backend(this, input, reader, writer, setRawMode);
+ TApplicationImpl();
+ }
+
+ /**
+ * Public constructor. The backend type will be BackendType.ECMA48.
+ *
+ * @param input the InputStream underlying 'reader'. Its available()
+ * method is used to determine if reader.read() will block or not.
+ * @param reader a Reader connected to the remote user.
+ * @param writer a PrintWriter connected to the remote user.
+ * @throws IllegalArgumentException if input, reader, or writer are null.
+ */
+ public TApplication(final InputStream input, final Reader reader,
+ final PrintWriter writer) {
+
+ this(input, reader, writer, false);
+ }
+
+ /**
+ * Public constructor. This hook enables use with new non-Jexer
+ * backends.
+ *
+ * @param backend a Backend that is already ready to go.
+ */
+ public TApplication(final Backend backend) {
+ this.backend = backend;
+ backend.setListener(this);
+ TApplicationImpl();
+ }
+
+ /**
+ * Finish construction once the backend is set.
+ */
+ private void TApplicationImpl() {
+ // Text block mouse option
+ if (System.getProperty("jexer.textMouse", "true").equals("false")) {
+ textMouse = false;
+ }
+
+ // Hide mouse when typing option
+ if (System.getProperty("jexer.hideMouseWhenTyping",
+ "false").equals("true")) {
+
+ hideMouseWhenTyping = true;
+ }
+
+ // Hide status bar option
+ if (System.getProperty("jexer.hideStatusBar",
+ "false").equals("true")) {
+ hideStatusBar = true;
+ }
+
+ // Hide menu bar option
+ if (System.getProperty("jexer.hideMenuBar", "false").equals("true")) {
+ hideMenuBar = true;
+ }
+
+ theme = new ColorTheme();
+ desktopTop = (hideMenuBar ? 0 : 1);
+ desktopBottom = getScreen().getHeight() - 1 + (hideStatusBar ? 1 : 0);
+ fillEventQueue = new LinkedList<TInputEvent>();
+ drainEventQueue = new LinkedList<TInputEvent>();
+ windows = new LinkedList<TWindow>();
+ menus = new ArrayList<TMenu>();
+ subMenus = new ArrayList<TMenu>();
+ timers = new LinkedList<TTimer>();
+ accelerators = new HashMap<TKeypress, TMenuItem>();
+ menuItems = new LinkedList<TMenuItem>();
+ desktop = new TDesktop(this);
+
+ // Special case: the Swing backend needs to have a timer to drive its
+ // blink state.
+ if ((backend instanceof SwingBackend)
+ || (backend instanceof MultiBackend)
+ ) {
+ // Default to 500 millis, unless a SwingBackend has its own
+ // value.
+ long millis = 500;
+ if (backend instanceof SwingBackend) {
+ millis = ((SwingBackend) backend).getBlinkMillis();
+ }
+ if (millis > 0) {
+ addTimer(millis, true,
+ new TAction() {
+ public void DO() {
+ TApplication.this.doRepaint();
+ }
+ }
+ );
+ }
+ }
+
+ }
+
+ // ------------------------------------------------------------------------
+ // Runnable ---------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Run this application until it exits.
+ */
+ public void run() {
+ // System.err.println("*** TApplication.run() begins ***");
+
+ // Start the screen updater thread
+ screenHandler = new ScreenHandler(this);
+ (new Thread(screenHandler)).start();
+
+ // Start the main consumer thread
+ primaryEventHandler = new WidgetEventHandler(this, true);
+ (new Thread(primaryEventHandler)).start();
+
+ started = true;
+
+ while (!quit) {
+ synchronized (this) {
+ boolean doWait = false;
+
+ if (!backend.hasEvents()) {
+ synchronized (fillEventQueue) {
+ if (fillEventQueue.size() == 0) {
+ doWait = true;
+ }
+ }
+ }
+
+ if (doWait) {
+ // No I/O to dispatch, so wait until the backend
+ // provides new I/O.
+ try {
+ if (debugThreads) {
+ System.err.println(System.currentTimeMillis() +
+ " " + Thread.currentThread() + " MAIN sleep");
+ }
+
+ this.wait();
+
+ if (debugThreads) {
+ System.err.println(System.currentTimeMillis() +
+ " " + Thread.currentThread() + " MAIN AWAKE");
+ }
+ } catch (InterruptedException e) {
+ // I'm awake and don't care why, let's see what's
+ // going on out there.
+ }
+ }
+
+ } // synchronized (this)
+
+ synchronized (fillEventQueue) {
+ // Pull any pending I/O events
+ backend.getEvents(fillEventQueue);
+
+ // Dispatch each event to the appropriate handler, one at a
+ // time.
+ for (;;) {
+ TInputEvent event = null;
+ if (fillEventQueue.size() == 0) {
+ break;
+ }
+ event = fillEventQueue.remove(0);
+ metaHandleEvent(event);
+ }
+ }
+
+ // Wake a consumer thread if we have any pending events.
+ if (drainEventQueue.size() > 0) {
+ wakeEventHandler();
+ }
+
+ } // while (!quit)
+
+ // Shutdown the event consumer threads
+ if (secondaryEventHandler != null) {
+ synchronized (secondaryEventHandler) {
+ secondaryEventHandler.notify();
+ }
+ }
+ if (primaryEventHandler != null) {
+ synchronized (primaryEventHandler) {
+ primaryEventHandler.notify();
+ }
+ }
+
+ // Close all the windows. This gives them an opportunity to release
+ // resources.
+ closeAllWindows();
+
+ // Close the desktop.
+ if (desktop != null) {
+ setDesktop(null);
+ }
+
+ // Give the overarching application an opportunity to release
+ // resources.
+ onExit();
+
+ // System.err.println("*** TApplication.run() exits ***");
+ }
+
+ // ------------------------------------------------------------------------
+ // Event handlers ---------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Method that TApplication subclasses can override to handle menu or
+ * posted command events.
+ *
+ * @param command command event
+ * @return if true, this event was consumed
+ */
+ protected boolean onCommand(final TCommandEvent command) {
+ // Default: handle cmExit
+ if (command.equals(cmExit)) {
+ if (messageBox(i18n.getString("exitDialogTitle"),
+ i18n.getString("exitDialogText"),
+ TMessageBox.Type.YESNO).isYes()) {
+
+ exit();
+ }
+ return true;
+ }
+
+ if (command.equals(cmShell)) {
+ openTerminal(0, 0, TWindow.RESIZABLE);
+ return true;
+ }
+
+ if (command.equals(cmTile)) {
+ tileWindows();
+ return true;
+ }
+ if (command.equals(cmCascade)) {
+ cascadeWindows();
+ return true;
+ }
+ if (command.equals(cmCloseAll)) {
+ closeAllWindows();
+ return true;
+ }
+
+ if (command.equals(cmMenu) && (hideMenuBar == false)) {
+ if (!modalWindowActive() && (activeMenu == null)) {
+ if (menus.size() > 0) {
+ menus.get(0).setActive(true);
+ activeMenu = menus.get(0);
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Method that TApplication subclasses can override to handle menu
+ * events.
+ *
+ * @param menu menu event
+ * @return if true, this event was consumed
+ */
+ protected boolean onMenu(final TMenuEvent menu) {
+
+ // Default: handle MID_EXIT
+ if (menu.getId() == TMenu.MID_EXIT) {
+ if (messageBox(i18n.getString("exitDialogTitle"),
+ i18n.getString("exitDialogText"),
+ TMessageBox.Type.YESNO).isYes()) {
+
+ exit();
+ }
+ return true;
+ }
+
+ if (menu.getId() == TMenu.MID_SHELL) {
+ openTerminal(0, 0, TWindow.RESIZABLE);
+ return true;
+ }
+
+ if (menu.getId() == TMenu.MID_TILE) {
+ tileWindows();
+ return true;
+ }
+ if (menu.getId() == TMenu.MID_CASCADE) {
+ cascadeWindows();
+ return true;
+ }
+ if (menu.getId() == TMenu.MID_CLOSE_ALL) {
+ closeAllWindows();
+ return true;
+ }
+ if (menu.getId() == TMenu.MID_ABOUT) {
+ showAboutDialog();
+ return true;
+ }
+ if (menu.getId() == TMenu.MID_REPAINT) {
+ getScreen().clearPhysical();
+ doRepaint();
+ return true;
+ }
+ if (menu.getId() == TMenu.MID_VIEW_IMAGE) {
+ openImage();
+ return true;
+ }
+ if (menu.getId() == TMenu.MID_SCREEN_OPTIONS) {
+ new TFontChooserWindow(this);
+ return true;
+ }
+
+ if (menu.getId() == TMenu.MID_CUT) {
+ postMenuEvent(new TCommandEvent(cmCut));
+ return true;
+ }
+ if (menu.getId() == TMenu.MID_COPY) {
+ postMenuEvent(new TCommandEvent(cmCopy));
+ return true;
+ }
+ if (menu.getId() == TMenu.MID_PASTE) {
+ postMenuEvent(new TCommandEvent(cmPaste));
+ return true;
+ }
+ if (menu.getId() == TMenu.MID_CLEAR) {
+ postMenuEvent(new TCommandEvent(cmClear));
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Method that TApplication subclasses can override to handle keystrokes.
+ *
+ * @param keypress keystroke event
+ * @return if true, this event was consumed
+ */
+ protected boolean onKeypress(final TKeypressEvent keypress) {
+ // Default: only menu shortcuts
+
+ // Process Alt-F, Alt-E, etc. menu shortcut keys
+ if (!keypress.getKey().isFnKey()
+ && keypress.getKey().isAlt()
+ && !keypress.getKey().isCtrl()
+ && (activeMenu == null)
+ && !modalWindowActive()
+ && (hideMenuBar == false)
+ ) {
+
+ assert (subMenus.size() == 0);
+
+ for (TMenu menu: menus) {
+ if (Character.toLowerCase(menu.getMnemonic().getShortcut())
+ == Character.toLowerCase(keypress.getKey().getChar())
+ ) {
+ activeMenu = menu;
+ menu.setActive(true);
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Process background events, and update the screen.
+ */
+ private void finishEventProcessing() {
+ if (debugThreads) {
+ System.err.printf(System.currentTimeMillis() + " " +
+ Thread.currentThread() + " finishEventProcessing()\n");
+ }
+
+ // See if we need to enable/disable the edit menu.
+ EditMenuUser widget = null;
+ if (activeMenu == null) {
+ if (activeWindow != null) {
+ if (activeWindow.getActiveChild() instanceof EditMenuUser) {
+ widget = (EditMenuUser) activeWindow.getActiveChild();
+ }
+ } else if (desktop != null) {
+ if (desktop.getActiveChild() instanceof EditMenuUser) {
+ widget = (EditMenuUser) desktop.getActiveChild();
+ }
+ }
+ if (widget == null) {
+ disableMenuItem(TMenu.MID_CUT);
+ disableMenuItem(TMenu.MID_COPY);
+ disableMenuItem(TMenu.MID_PASTE);
+ disableMenuItem(TMenu.MID_CLEAR);
+ } else {
+ if (widget.isEditMenuCut()) {
+ enableMenuItem(TMenu.MID_CUT);
+ } else {
+ disableMenuItem(TMenu.MID_CUT);
+ }
+ if (widget.isEditMenuCopy()) {
+ enableMenuItem(TMenu.MID_COPY);
+ } else {
+ disableMenuItem(TMenu.MID_COPY);
+ }
+ if (widget.isEditMenuPaste()) {
+ enableMenuItem(TMenu.MID_PASTE);
+ } else {
+ disableMenuItem(TMenu.MID_PASTE);
+ }
+ if (widget.isEditMenuClear()) {
+ enableMenuItem(TMenu.MID_CLEAR);
+ } else {
+ disableMenuItem(TMenu.MID_CLEAR);
+ }
+ }
+ }
+
+ // Process timers and call doIdle()'s
+ doIdle();
+
+ // Update the screen
+ synchronized (getScreen()) {
+ drawAll();
+ }
+
+ // Wake up the screen repainter
+ wakeScreenHandler();
+
+ if (debugThreads) {
+ System.err.printf(System.currentTimeMillis() + " " +
+ Thread.currentThread() + " finishEventProcessing() END\n");
+ }
+ }
+
+ /**
+ * Peek at certain application-level events, add to eventQueue, and wake
+ * up the consuming Thread.
+ *
+ * @param event the input event to consume
+ */
+ private void metaHandleEvent(final TInputEvent event) {
+
+ if (debugEvents) {
+ System.err.printf(String.format("metaHandleEvents event: %s\n",
+ event)); System.err.flush();
+ }
+
+ if (quit) {
+ // Do no more processing if the application is already trying
+ // to exit.
+ return;
+ }
+
+ // Special application-wide events -------------------------------
+
+ // Abort everything
+ if (event instanceof TCommandEvent) {
+ TCommandEvent command = (TCommandEvent) event;
+ if (command.equals(cmAbort)) {
+ exit();
+ return;
+ }
+ }
+
+ synchronized (drainEventQueue) {
+ // Screen resize
+ if (event instanceof TResizeEvent) {
+ TResizeEvent resize = (TResizeEvent) event;
+ synchronized (getScreen()) {
+ if ((System.currentTimeMillis() - screenResizeTime >= 15)
+ || (resize.getWidth() < getScreen().getWidth())
+ || (resize.getHeight() < getScreen().getHeight())
+ ) {
+ getScreen().setDimensions(resize.getWidth(),
+ resize.getHeight());
+ screenResizeTime = System.currentTimeMillis();
+ }
+ desktopBottom = getScreen().getHeight() - 1;
+ if (hideStatusBar) {
+ desktopBottom++;
+ }
+ mouseX = 0;
+ mouseY = 0;
+ oldMouseX = 0;
+ oldMouseY = 0;
+ }
+ if (desktop != null) {
+ desktop.setDimensions(0, desktopTop, resize.getWidth(),
+ (desktopBottom - desktopTop));
+ desktop.onResize(resize);
+ }
+
+ // Change menu edges if needed.
+ recomputeMenuX();
+
+ // We are dirty, redraw the screen.
+ doRepaint();
+
+ /*
+ System.err.println("New screen: " + resize.getWidth() +
+ " x " + resize.getHeight());
+ */
+ return;
+ }
+
+ // Put into the main queue
+ drainEventQueue.add(event);
+ }
+ }
+
+ /**
+ * Dispatch one event to the appropriate widget or application-level
+ * event handler. This is the primary event handler, it has the normal
+ * application-wide event handling.
+ *
+ * @param event the input event to consume
+ * @see #secondaryHandleEvent(TInputEvent event)
+ */
+ private void primaryHandleEvent(final TInputEvent event) {
+
+ if (debugEvents) {
+ System.err.printf("%s primaryHandleEvent: %s\n",
+ Thread.currentThread(), event);
+ }
+ TMouseEvent doubleClick = null;
+
+ // Special application-wide events -----------------------------------
+
+ if (event instanceof TKeypressEvent) {
+ if (hideMouseWhenTyping) {
+ typingHidMouse = true;
+ }
+ }
+
+ // Peek at the mouse position
+ if (event instanceof TMouseEvent) {
+ typingHidMouse = false;
+
+ TMouseEvent mouse = (TMouseEvent) event;
+ if (mouse.isMouse1() && (mouse.isShift() || mouse.isCtrl())) {
+ // Screen selection.
+ if (inScreenSelection) {
+ screenSelectionX1 = mouse.getX();
+ screenSelectionY1 = mouse.getY();
+ } else {
+ inScreenSelection = true;
+ screenSelectionX0 = mouse.getX();
+ screenSelectionY0 = mouse.getY();
+ screenSelectionX1 = mouse.getX();
+ screenSelectionY1 = mouse.getY();
+ screenSelectionRectangle = mouse.isCtrl();
+ }
+ } else {
+ if (inScreenSelection) {
+ getScreen().copySelection(clipboard, screenSelectionX0,
+ screenSelectionY0, screenSelectionX1, screenSelectionY1,
+ screenSelectionRectangle);
+ }
+ inScreenSelection = false;
+ }
+
+ if ((mouseX != mouse.getX()) || (mouseY != mouse.getY())) {
+ oldMouseX = mouseX;
+ oldMouseY = mouseY;
+ mouseX = mouse.getX();
+ mouseY = mouse.getY();
+ } else {
+ if ((mouse.getType() == TMouseEvent.Type.MOUSE_DOWN)
+ && (!mouse.isMouseWheelUp())
+ && (!mouse.isMouseWheelDown())
+ ) {
+ if ((mouse.getTime().getTime() - lastMouseUpTime) <
+ doubleClickTime) {
+
+ // This is a double-click.
+ doubleClick = new TMouseEvent(TMouseEvent.Type.
+ MOUSE_DOUBLE_CLICK,
+ mouse.getX(), mouse.getY(),
+ mouse.getAbsoluteX(), mouse.getAbsoluteY(),
+ mouse.isMouse1(), mouse.isMouse2(),
+ mouse.isMouse3(),
+ mouse.isMouseWheelUp(), mouse.isMouseWheelDown(),
+ mouse.isAlt(), mouse.isCtrl(), mouse.isShift());
+
+ } else {
+ // The first click of a potential double-click.
+ lastMouseUpTime = mouse.getTime().getTime();
+ }
+ }
+ }
+
+ // See if we need to switch focus to another window or the menu
+ checkSwitchFocus((TMouseEvent) event);
+ }
+
+ // Handle menu events
+ if ((activeMenu != null) && !(event instanceof TCommandEvent)) {
+ TMenu menu = activeMenu;
+
+ if (event instanceof TMouseEvent) {
+ TMouseEvent mouse = (TMouseEvent) event;
+
+ while (subMenus.size() > 0) {
+ TMenu subMenu = subMenus.get(subMenus.size() - 1);
+ if (subMenu.mouseWouldHit(mouse)) {
+ break;
+ }
+ if ((mouse.getType() == TMouseEvent.Type.MOUSE_MOTION)
+ && (!mouse.isMouse1())
+ && (!mouse.isMouse2())
+ && (!mouse.isMouse3())
+ && (!mouse.isMouseWheelUp())
+ && (!mouse.isMouseWheelDown())
+ ) {
+ break;
+ }
+ // We navigated away from a sub-menu, so close it
+ closeSubMenu();
+ }
+
+ // Convert the mouse relative x/y to menu coordinates
+ assert (mouse.getX() == mouse.getAbsoluteX());
+ assert (mouse.getY() == mouse.getAbsoluteY());
+ if (subMenus.size() > 0) {
+ menu = subMenus.get(subMenus.size() - 1);
+ }
+ mouse.setX(mouse.getX() - menu.getX());
+ mouse.setY(mouse.getY() - menu.getY());
+ }
+ menu.handleEvent(event);
+ return;
+ }
+
+ if (event instanceof TKeypressEvent) {
+ TKeypressEvent keypress = (TKeypressEvent) event;
+
+ // See if this key matches an accelerator, and is not being
+ // shortcutted by the active window, and if so dispatch the menu
+ // event.
+ boolean windowWillShortcut = false;
+ if (activeWindow != null) {
+ assert (activeWindow.isShown());
+ if (activeWindow.isShortcutKeypress(keypress.getKey())) {
+ // We do not process this key, it will be passed to the
+ // window instead.
+ windowWillShortcut = true;
+ }
+ }
+
+ if (!windowWillShortcut && !modalWindowActive()) {
+ TKeypress keypressLowercase = keypress.getKey().toLowerCase();
+ TMenuItem item = null;
+ synchronized (accelerators) {
+ item = accelerators.get(keypressLowercase);
+ }
+ if (item != null) {
+ if (item.isEnabled()) {
+ // Let the menu item dispatch
+ item.dispatch();
+ return;
+ }
+ }
+
+ // Handle the keypress
+ if (onKeypress(keypress)) {
+ return;
+ }
+ }
+ }
+
+ if (event instanceof TCommandEvent) {
+ if (onCommand((TCommandEvent) event)) {
+ return;
+ }
+ }
+
+ if (event instanceof TMenuEvent) {
+ if (onMenu((TMenuEvent) event)) {
+ return;
+ }
+ }
+
+ // Dispatch events to the active window -------------------------------
+ boolean dispatchToDesktop = true;
+ TWindow window = activeWindow;
+ if (window != null) {
+ assert (window.isActive());
+ assert (window.isShown());
+ if (event instanceof TMouseEvent) {
+ TMouseEvent mouse = (TMouseEvent) event;
+ // Convert the mouse relative x/y to window coordinates
+ assert (mouse.getX() == mouse.getAbsoluteX());
+ assert (mouse.getY() == mouse.getAbsoluteY());
+ mouse.setX(mouse.getX() - window.getX());
+ mouse.setY(mouse.getY() - window.getY());
+
+ if (doubleClick != null) {
+ doubleClick.setX(doubleClick.getX() - window.getX());
+ doubleClick.setY(doubleClick.getY() - window.getY());
+ }
+
+ if (window.mouseWouldHit(mouse)) {
+ dispatchToDesktop = false;
+ }
+ } else if (event instanceof TKeypressEvent) {
+ dispatchToDesktop = false;
+ } else if (event instanceof TMenuEvent) {
+ dispatchToDesktop = false;
+ }
+
+ if (debugEvents) {
+ System.err.printf("TApplication dispatch event: %s\n",
+ event);
+ }
+ window.handleEvent(event);
+ if (doubleClick != null) {
+ window.handleEvent(doubleClick);
+ }
+ }
+ if (dispatchToDesktop) {
+ // This event is fair game for the desktop to process.
+ if (desktop != null) {
+ desktop.handleEvent(event);
+ if (doubleClick != null) {
+ desktop.handleEvent(doubleClick);
+ }
+ }
+ }
+ }
+
+ /**
+ * Dispatch one event to the appropriate widget or application-level
+ * event handler. This is the secondary event handler used by certain
+ * special dialogs (currently TMessageBox and TFileOpenBox).
+ *
+ * @param event the input event to consume
+ * @see #primaryHandleEvent(TInputEvent event)
+ */
+ private void secondaryHandleEvent(final TInputEvent event) {
+ TMouseEvent doubleClick = null;
+
+ if (debugEvents) {
+ System.err.printf("%s secondaryHandleEvent: %s\n",
+ Thread.currentThread(), event);
+ }
+
+ // Peek at the mouse position
+ if (event instanceof TMouseEvent) {
+ typingHidMouse = false;
+
+ TMouseEvent mouse = (TMouseEvent) event;
+ if ((mouseX != mouse.getX()) || (mouseY != mouse.getY())) {
+ oldMouseX = mouseX;
+ oldMouseY = mouseY;
+ mouseX = mouse.getX();
+ mouseY = mouse.getY();
+ } else {
+ if ((mouse.getType() == TMouseEvent.Type.MOUSE_DOWN)
+ && (!mouse.isMouseWheelUp())
+ && (!mouse.isMouseWheelDown())
+ ) {
+ if ((mouse.getTime().getTime() - lastMouseUpTime) <
+ doubleClickTime) {
+
+ // This is a double-click.
+ doubleClick = new TMouseEvent(TMouseEvent.Type.
+ MOUSE_DOUBLE_CLICK,
+ mouse.getX(), mouse.getY(),
+ mouse.getAbsoluteX(), mouse.getAbsoluteY(),
+ mouse.isMouse1(), mouse.isMouse2(),
+ mouse.isMouse3(),
+ mouse.isMouseWheelUp(), mouse.isMouseWheelDown(),
+ mouse.isAlt(), mouse.isCtrl(), mouse.isShift());
+
+ } else {
+ // The first click of a potential double-click.
+ lastMouseUpTime = mouse.getTime().getTime();
+ }
+ }
+ }
+ }
+
+ secondaryEventReceiver.handleEvent(event);
+ // Note that it is possible for secondaryEventReceiver to be null
+ // now, because its handleEvent() might have finished out on the
+ // secondary thread. So put any extra processing inside a null
+ // check.
+ if (secondaryEventReceiver != null) {
+ if (doubleClick != null) {
+ secondaryEventReceiver.handleEvent(doubleClick);
+ }
+ }
+ }
+
+ /**
+ * Enable a widget to override the primary event thread.