package jDraft;

/*
* Copyright 2007 Kustaa Nyholm
*
*  Licensed under the Apache License, Version 2.0 (the "License");
*  you may not use this file except in compliance with the License.
*  You may obtain a copy of the License at
*
*       http://www.apache.org/licenses/LICENSE-2.0
*
*   Unless required by applicable law or agreed to in writing, software
*   distributed under the License is distributed on an "AS IS" BASIS,
*   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*   See the License for the specific language governing permissions and
*   limitations under the License.
*/
import jApp.*;

import java.awt.*;
import java.awt.event.*;

import javax.swing.*;
import javax.swing.FocusManager;

import java.util.*;

/**
* Implements a simple API to attach callbacks to global even dispatching.
*
<p>
* GlobalEventHook makes it possible to attach global event listeners and
* actions for
<code>MouseEvent</code> and <code>KeyEvent</code> events.
*
* While most event handling is best done using the standard AWT/Swing methods,
* there isfunctionality that is cumbersome to implement using the standard
* event handling framework.
*
<p>
* Many applications implement mouse actions that can be modified by holding one
* or more of the modifiers keys down. For an example consider a zoom command
* where clicking with the mouse causes zoom in or out depending on the state of
* the ALT-key. Good UI design practice dictates that the cursor shape changes
* accordingly. This is easily implemented by creating a
<code>setCursor</code>
* -method that is called from within the mouse listener code. However if the
* user does not touch the mouse but simply presses the ALT-key the cursor does
* not change shape accordingly as no mouse events are generated for pressing or
* releasing modifier keys. Getting the mouse listener code to receive key
* events can be tricky because of the complexities of key event dispatching and
* focus management.
*
<p>
* One of the main functions for the <code>GlobalEventHook</code> is to
* generate those missing mouse moved events when the modifier keys change
* state.
*
<p>
* Also there are times at which some far removed code in the application
* necessiates the update of the cursor. Instead of implementing this manually
* it would be much better just fake a mouse movement in effect taking advantage
* of the standard mouse event dispatching and chain of command.
*
<p>
* The <code>GlobalEventHook</code> implements
*
{@link #postMouseNotMovedEvent()} just for that purpose.
*
<p>
* An other service provided by the <code>GlobalEventHook</code> is access to
* the current state of the modifier keys. While most of the time the modifiers
* are available in the context where they are needed, in the key and mouse
* listeners, this is not always the case.
*
<p>
* The state of the current modifiers is available from the
*
<code>InputEvent</code> return by the {@link #getLastInputEvent()}.
*
<p>
* Yet an other use for the <code>GlobalEventHook</code> is to create global
* short cuts that are always available regardless of the focus management.
* Obviously this should be used sparingly as it completely bypasses the focus
* management system but there are conditions in which this is justfiable.
*
<p>
* For this purpose a global <code>InputMap</code> and <code>ActionMap</code>
* is available through the {@link #getActionMap()} and {@link #getInputMap()}
* methods.
*/

final public class GlobalEventHook {
 
private static EventQueueHook m_Hook;

 
private static InputMap m_InputMap = new InputMap();

 
private static ActionMap m_Actions = new ActionMap();

 
private static AWTEvent m_LastEvent;

 
private static InputEvent m_LastInputEvent;

 
private static KeyEvent m_LastKeyEvent;

 
private static MouseEvent m_LastMouseEvent;

 
private static LinkedList<KeyListener> m_KeyListeners = new LinkedList();

 
private static LinkedList<MouseListener> m_MouseListeners = new LinkedList();

 
private static LinkedList<MouseMotionListener> m_MouseMotionListeners = new LinkedList();

 
private GlobalEventHook() {
  }

 
/**
   * Gets the global
<code>InputMap</code> that maps <code>KyeStroke</code>s
   * to action keys.
   *
   *
@return the global input map
   */
 
static public InputMap getInputMap() {
   
return m_InputMap;
 
}

 
/**
   * Gets the global
<code>ActionMap</code> that maps
   *
<code>action keys</code>s to <code>Action</action>.
   *
@return the global action map
   */
 
static public ActionMap getActionMap() {
   
return m_Actions;
 
}

 
/**
   * Returns the last event dispatched.
   *
   *
@return the last event dispatched
   */
 
static public AWTEvent getLastEvent() {
   
return m_LastEvent;
 
}

 
/**
   * Returns the last input event dispatched.
   *
   *
@return the last input event dispatched
   */
 
static public InputEvent getLastInputEvent() {
   
return m_LastInputEvent;
 
}

 
/**
   * Returns the last key event dispatched.
   *
   *
@return the last key event dispatched
   */
 
static public KeyEvent getLastKeyEvent() {
   
return m_LastKeyEvent;
 
}

 
/**
   * Returns the last mouse event dispatched.
   *
   *
@return the last mouse event dispatched
   */
 
static public MouseEvent getLastMouseEvent() {
   
return m_LastMouseEvent;
 
}

 
/**
   * Adds a global key listener
   *
   *
@param l
   *            the key listener to add
   */
 
static public void addKeyListener(KeyListener l) {
   
m_KeyListeners.add(l);
 
}

 
/**
   * Removes a global key listener
   *
   *
@param l
   *            the key listener to remove
   */
 
static public void removeKeyListener(KeyListener l) {
   
m_KeyListeners.remove(l);
 
}

 
/**
   * Adds a global mouse listener
   *
   *
@param l
   *            the mouse listener to add
   */
 
static public void addMouseListener(MouseListener l) {
   
m_MouseListeners.add(l);
 
}

 
/**
   * Removes a global mouse listener
   *
   *
@param l
   *            the mouse listener to remove
   */
 
static public void removeMouseListener(MouseListener l) {
   
m_MouseListeners.remove(l);
 
}

 
/**
   * Adds a global mouse motion listener
   *
   *
@param l
   *            the mouse motion listener to add
   */
 
static public void addMouseMotionListener(MouseMotionListener l) {
   
m_MouseMotionListeners.add(l);
 
}

 
/**
   * Removes a global mouse motion listener
   *
   *
@param l
   *            the mouse motion listener to remove
   */
 
static public void removeMouseMotionListener(MouseMotionListener l) {
   
m_MouseMotionListeners.remove(l);
 
}

 
private static void dispatchMouseEvent(MouseEvent me) {
   
switch (me.getID()) {
     
case MouseEvent.MOUSE_CLICKED:
       
for (MouseListener ml : m_MouseListeners)
         
ml.mouseClicked(me);
       
break;
     
case MouseEvent.MOUSE_ENTERED:
       
for (MouseListener ml : m_MouseListeners)
         
ml.mouseEntered(me);
       
break;
     
case MouseEvent.MOUSE_EXITED:
       
for (MouseListener ml : m_MouseListeners)
         
ml.mouseExited(me);
       
break;
     
case MouseEvent.MOUSE_PRESSED:
       
for (MouseListener ml : m_MouseListeners)
         
ml.mousePressed(me);
       
break;
     
case MouseEvent.MOUSE_RELEASED:
       
for (MouseListener ml : m_MouseListeners)
         
ml.mouseReleased(me);
       
break;
     
case MouseEvent.MOUSE_DRAGGED:
       
for (MouseMotionListener ml : m_MouseMotionListeners)
         
ml.mouseDragged(me);
       
break;
     
case MouseEvent.MOUSE_MOVED:
       
for (MouseMotionListener ml : m_MouseMotionListeners)
         
ml.mouseMoved(me);
       
break;

   
}
  }

 
private static void dispatchKeyEvent(KeyEvent ke) {
   
switch (ke.getID()) {
     
case KeyEvent.KEY_PRESSED:
       
for (KeyListener kl : m_KeyListeners)
         
kl.keyPressed(ke);
       
break;
     
case KeyEvent.KEY_TYPED:
       
for (KeyListener kl : m_KeyListeners)
         
kl.keyTyped(ke);
       
break;
     
case KeyEvent.KEY_RELEASED:
       
for (KeyListener kl : m_KeyListeners)
         
kl.keyReleased(ke);
       
break;

   
}
  }

 
private static void fakeMouseNotMovedEvent() {
   
if (m_LastMouseEvent == null)
     
return;
   
int em = m_LastMouseEvent.getModifiersEx();
   
boolean down = (em & (MouseEvent.BUTTON1_DOWN_MASK | MouseEvent.BUTTON2_DOWN_MASK | MouseEvent.BUTTON3_DOWN_MASK)) != 0;
    m_Hook.postEvent
(new MouseEvent(//
       
(Component) m_LastMouseEvent.getSource(), //
       
down ? MouseEvent.MOUSE_DRAGGED : MouseEvent.MOUSE_MOVED, //
       
m_LastMouseEvent.getWhen(), m_LastMouseEvent.getModifiers(), //
       
m_LastMouseEvent.getX(), //
       
m_LastMouseEvent.getY(),//
       
0, //
       
false, //
       
MouseEvent.NOBUTTON));

 
}

 
public static void postMouseNotMovedEvent() {
   
fakeMouseNotMovedEvent();
 
}

 
/**
   * Initilizes the GlobalEventHook single instance.
   *
<p>
  
* Calling this method should be one of the first actions when an
   * application starts up, certainly before any AWT/UI code is executed.
   * GlobalEventHook works by extending EventQueue and pushing an instance of
   * itself to the front of the system event queue thus having a first go at
   * each event as they are dispatched.
   *
   *
   */
 
public static void init() {
   
if (m_Hook == null) {
     
m_Hook = new EventQueueHook();
      Toolkit.getDefaultToolkit
().getSystemEventQueue().push(m_Hook);
   
}
  }

 
private static class EventQueueHook extends EventQueue {
   
protected void dispatchEvent(AWTEvent event) {
     
MouseEvent fakedEvent = null;
     
if (event instanceof KeyEvent) {
       
KeyEvent keyEvent = (KeyEvent) event;
       
if ((m_LastKeyEvent == null || keyEvent.getModifiers() != m_LastKeyEvent.getModifiers()) && m_LastMouseEvent != null) {
         
int em = m_LastMouseEvent.getModifiersEx();
         
boolean down = (em & (MouseEvent.BUTTON1_DOWN_MASK | MouseEvent.BUTTON2_DOWN_MASK | MouseEvent.BUTTON3_DOWN_MASK)) != 0;
          fakedEvent =
new MouseEvent(//
             
(Component) m_LastMouseEvent.getSource(), //
             
down ? MouseEvent.MOUSE_DRAGGED : MouseEvent.MOUSE_MOVED, //
             
keyEvent.getWhen(), //
             
keyEvent.getModifiers(), //
             
m_LastMouseEvent.getX(), //
             
m_LastMouseEvent.getY(),//
             
0, //
             
false, //
             
MouseEvent.NOBUTTON);
       
}
      }

     
if (event instanceof KeyEvent)
       
m_LastKeyEvent = (KeyEvent) event;
     
if (event instanceof InputEvent)
       
m_LastInputEvent = (InputEvent) event;
     
if (event instanceof MouseEvent)
       
m_LastMouseEvent = (MouseEvent) event;

     
if (fakedEvent != null) {
       
super.dispatchEvent(fakedEvent);
     
}

     
if (event instanceof KeyEvent) {
       
KeyEvent keyEvent = (KeyEvent) event;
        KeyStroke ks = KeyStroke.getKeyStrokeForEvent
((KeyEvent) event);
        Object actionKey = m_InputMap.get
(ks);
       
if (actionKey != null) {
         
Action action = m_Actions.get(actionKey);
         
if (action != null && action.isEnabled()) {
           
String actionString = null;
           
if (actionKey instanceof String)
             
actionString = (String) actionKey;
            action.actionPerformed
(new ActionEvent(event.getSource(), event.getID(), actionString, ((KeyEvent) event).getModifiers()));
           
return;
         
}

        }
      }

     
if (event instanceof KeyEvent) {
       
dispatchKeyEvent(m_LastKeyEvent);
     
}
     
if (event instanceof MouseEvent) {
       
dispatchMouseEvent(m_LastMouseEvent);
     
}
     
m_LastEvent = event;
     
super.dispatchEvent(event);
   
}
  }

}