package psychWithJava;
/*
* NormalWindow.java
*
* Copyright (C) 2005-2008 Huseyin Boyaci. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation; either version 2 of the License, or (at your option) any later
* version. This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE.
*
* See the GNU General Public License version 2 for more details
* (a copy is included in the LICENSE file that accompanied this code)
* (also available at http://www.gnu.org) You should have received a copy of
* the GNU General Public License along with this program; if not, write to
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*
*/
import psychWithJava.FullScreen;
import java.applet.Applet;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.font.FontRenderContext;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.awt.image.VolatileImage;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import javax.swing.JApplet;
import javax.swing.JFrame;
import javax.swing.JPanel;
/**
* NormalWindow
class provides methods to display visual stimuli
* and interact with the observer in a normal window environment.
*
* Unlike FullScreen
, NormalWindow doesn't operate in
* Full Screen Exclusive Mode. However the method signitures are identical
* to those in FullScreen class, which makes it simple to convert a
* FullScreen application into a normal window application. A normal window
* application has several benefits. One benefit is that once an experiment is
* converted into a normal window application it can
* easily be shared with collegues and placed on a web page.
*
* Some methods of FullScreen class do not exist in NormalWindow. * Those methods specific to Full Screen Exclusive Mode are: setNBuffers(), * getNBuffers(), closeScreen(), isFullScreenSupported(), * setDisplayMode(), isDisplayModeAvailable(), reportDisplayMode(), * getDisplayMode(), isDisplayChangeSupported(), reportDisplayModes(), * getDisplayModes(). Also there is no constructor in NormalWindow where one * can specify the screenID. *
* On the other hand there are additional methods specific to NormalWindow class. * Most notably setPassiveRendering() determines whether to use passive * rendering or active rendering. By default a NormalWindow object uses * passive rendering, whereas rendering in FullScreen is always active. In * active rendering the programmer has the complete control of the graphics * interface. In passive rendering the operating system and JVM (Java virtual * machine) may intervene and redraw the client's window when necessary. This is * useful in a normal window application because windows can be minimized * and maximized back, or can get hidden and visible again. In passive rendering * JVM updates the client's window automatically whenever necessary, * for example when it gets visible again after being hidden behind other * windows or minimized. This is useful in normal window but not a concern in * Full Screen Mode. Other specific method is isPassiveRendering(), which * reports whether or not the NormalWindow object uses passive rendering. *
* Whether passive or active rendering, NormalWindow always uses double * buffering. *
* Converting a FullScreen experiment into NormalWindow application. * NormalWindow inherits from JPanel of core Java library. A JPanel object has * to be placed in a lower level container before displayed on screen. A * suitable container is JFrame. The program can create a JFrame and then * place the new NormalWindow object inside it. Also the invokation of * FullScreen specific methods should be eliminated (setNBuffers() etc.). * In the end FullScreen's closeScreen() method should be eliminated * and the JFrame's dispose() method should be invoked. For example: *
// HPWindow extends NormalWindow, instead of FullScreen public class HPWindow extends NormalWindow implements Runnable { static JFrame mainFrame; static final int XO = 100; static final int YO = 100; static final int W = 612; static final int H = 612; public static void main(String[] args) { HPWindow nw = new HPWindow(); // The only addition/change to FullScreen is here: mainFrame = new JFrame(); mainFrame.setBounds(XO,YO,W,H); mainFrame.add(nw); mainFrame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); // Next two lines are optional mainFrame.setResizable(false); mainFrame.setTitle("Hello Psychophysicist"); // you must set it visible mainFrame.setVisible(true); // up to here // ... } public void run(){ try { // methods are identical to those in FullScreen displayText("Hello Psychophysicist (Normal Window)"); // .... } finally { // and replace this //fs.closeScreen(); mainFrame.dispose(); } } }*
* * Some demos using the NormalWindow class can be found here. * See also the implementation in greater * detail in * Chapter 11: Applets, normal window applications, * packaging and sharing your work * in * The Guide to psychophysics programming with Java. *
* Matlab and Mathematica development:
*
* If your window is resizable probably it is best to leave passive
* rendering on.
*
* @param pr new passive rendering value, true or false
*/
public void setPassiveRendering(boolean pr){
passiveRendering = pr;
setIgnoreRepaint(!passiveRendering);
}
/**
* Returns the current passive rendering value. True if passive rendering
* is on, false if active rendering.
*
* @return current passive rendering value.
*/
public boolean isPassiveRendering(){
return passiveRendering;
}
/**
* Updates the entire window by bringing the back video buffer front.
* NormalWindow always uses double buffering.
* The methods displayImage(), displayText()
* or blankScreen() (see below) always manipulate the back (invisible)
* video bufer.
* Upon invoking those methods
* user has to invoke this updateScreen() method to actually
* bring the back buffer to front, in other words to make it
* actually visible
* on the screen device.
*
* @see #displayImage(int, int, BufferedImage)
* @see #displayText(int, int, String)
* @see #blankScreen()
*
*/
public void updateScreen(){
if(screenImage == null)
return;
if(passiveRendering)
repaint();
else {
Graphics g = getGraphics();
try {
if(g!=null)
g.drawImage(screenImage, 0,0, this);
}
finally {
g.dispose();
}
}
}
protected void paintComponent(Graphics g){
if(passiveRendering){
super.paintComponent(g);
if(screenImage != null)
g.drawImage(screenImage,0,0,this);
}
}
/**
* Displays a BufferedImage at the center of the window.
* Note that
* this method draws the image on the back buffer. Therefore
* user has to invoke the updateScreen() method
* to actually display the image on the screen.
*
* @param bi BufferedImage to display
*
* @see #displayImage(int, int, BufferedImage)
* @see #updateScreen()
*/
public void displayImage(BufferedImage bi) {
if( bi!=null){
double x = (getWidth() - bi.getWidth()) / 2;
double y = (getHeight() - bi.getHeight()) / 2;
displayImage((int) x, (int) y, bi);
}
}
/**
* Displays a BufferedImage at the specified position.
* Note that
* this method draws the image on the back buffer. Therefore
* user has to invoke the updateScreen() method
* to actually display the image on the screen.
*
* @param x horizontal offset of the upper left corner of the image from the
* upper left corner of the window
* @param y vertical offset of the upper left corner of the image from the
* upper left corner of the window
* @param bi BufferedImage to display
*
* @see #displayImage(BufferedImage)
* @see #updateScreen()
*/
public void displayImage(int x, int y, BufferedImage bi) {
createScreenImage();
Graphics2D g = screenImage.createGraphics();
try {
if(g!=null && bi!=null)
g.drawImage(bi, x, y, null);
//Toolkit.getDefaultToolkit().sync(); // ???
}
finally {
g.dispose();
}
}
/**
* Displays text at the center of the window.
* Note that
* this method draws the text on the back buffer. Therefore
* user has to invoke the updateScreen() method
* to actually display the text on the screen.
*
* @param text a text message to display
*
* @see #displayText(int, int, String)
* @see #updateScreen()
*/
public void displayText(String text) {
createScreenImage();
Graphics2D g = screenImage.createGraphics();
Font font = getFont();
g.setFont(font);
FontRenderContext context = g.getFontRenderContext();
Rectangle2D bounds = font.getStringBounds(text, context);
double x = (getWidth() - bounds.getWidth()) / 2;
double y = (getHeight() - bounds.getHeight()) / 2;
double ascent = -bounds.getY();
double baseY = y + ascent;
displayText((int) x, (int) baseY, text);
}
/**
* Displays text at the specified position.
* Note that
* this method draws the text on the back buffer. Therefore
* user has to invoke the updateScreen() method
* to actually display the text on the screen.
*
* @param x horizontal offset of the upper left corner of the text from the
* upper left corner of the window
* @param y vertical offset of the upper left corner of the text from the
* upper left corner of the window
* @param text a text message to display
*
* @see #displayText(String)
* @see #updateScreen()
*/
public void displayText(int x, int y, String text) {
createScreenImage();
Graphics2D g = screenImage.createGraphics();
try {
g.setFont(getFont());
g.setPaint(getForeground());
g.drawString(text, x, y);
}
finally {
g.dispose();
}
}
/**
* Blanks the whole window using the current background color.
* Note that
* this method blanks the back buffer. Therefore
* user has to invoke the updateScreen() method
* to actually blank the screen.
*
* @see #updateScreen()
*
*/
public void blankScreen() {
createScreenImage();
Graphics2D g = screenImage.createGraphics();
try {
g.setColor(getBackground());
g.fillRect(0, 0, getWidth(), getHeight());
}
finally {
g.dispose();
}
}
/**
* Returns the current background color.
*
* @return current background color
*
* @see #setBackground(Color)
*
*/
public Color getBackground() {
return bgColor;
}
/**
* Sets the background color.
*
* @param bg new background color
*
* @see #getBackground()
*/
public void setBackground(Color bg) {
bgColor = bg;
}
/**
* Renders the cursor invisible.
*
* @see #showCursor()
*
*/
public void hideCursor() {
Cursor noCursor = null;
Toolkit tk = Toolkit.getDefaultToolkit();
Dimension d = tk.getBestCursorSize(1, 1);
if ((d.width | d.height) != 0)
noCursor = tk.createCustomCursor(new BufferedImage(d.width, d.height,
BufferedImage.TYPE_INT_ARGB), new Point(0, 0), "noCursor");
setCursor(noCursor);
}
/**
* Renders the cursor visible using default cursor
*
* @see #hideCursor()
*
*/
public void showCursor() {
setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
}
public void closeScreen() {
// if we are in a Window let's release it
if(getTopLevelAncestor() instanceof Window)
((Window)getTopLevelAncestor()).dispose();
// it doesn't make sense to do anything if we are
// in an Applet
/*else if (getTopLevelAncestor() instanceof Applet){
((Applet)getTopLevelAncestor()).destroy();
System.err.println("destroy");
}*/
else
return;
}
/**
* Returns the key typed by the observer.
*
* If the specified wait time is
* positive: This either (i) method returns the top element in the
* keyTyped queue immediately if there is
* at least one element in the keyTyped event queue or
* (ii) waits up to the specified amount of time for an element
* to become available. If no key is typed
* within the specified amount of time
* it returns null.
*
* If the specified time is zero: Returns the top element in
* the keyTyped event queue or null if queue is empty.
*
* If the specified wait time is negative: This method
* either returns the top element in the queue or if the queue is
* empty it waits indefinetely untill the observer types.
*
* In all cases, the element returned is removed from the event queue.
*
*
* General principles of event handling in FullScreen:
* FullScreen captures the key events in a seperate Thread and stores
* them in Thread safe BlockingQueue objects. Anytime observer
* types, presses or releases a key, that key and the time of the
* event are inserted to the end (tail)
* of the respective queues. When one of the getKeyTyped(), getKeyPressed()
* or getKeyReleased() methods is invoked, the top (head) of the respective
* queue is retrived and removed. Similarly, getWhenKeyTyped(),
* getWhenKeyPressed() and getWhenKeyReleased() methods retrive and remove
* the head in event time queues. flushKeyTyped(), flushKeyPressed() and
* flushKeyReleased() methods clear all queues, including the event time queues.
*
* For more information see
* Chapter 6: Getting observer response of the
* Guide to Psychophysics programming with Java.
*
* @param ms time in milliseconds to wait for a response
*
* @return the key typed
*
* @see #getKeyTyped()
* @see #flushKeyTyped()
* @see #getWhenKeyTyped()
*/
public String getKeyTyped(long ms){
String c = null;
try {
if(ms < 0)
c = keyTyped.take();
else
c = keyTyped.poll(ms, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
e.printStackTrace();
}
return c;
}
/**
* Returns the key typed by the observer.
* Returns the top element in keyTyped
* queue or null if queue is empty. Equivelent to invoking getKeyTyped(0).
*
* The element returned is removed from the event queue. See also
* general principles of event handling in FullScreen
* above.
*
* For more information see
* Chapter 6: Getting observer response of the
* Guide to Psychophysics programming with Java.
*
* @return the key typed
*
* @see #getKeyTyped(long)
* @see #flushKeyTyped()
* @see #getWhenKeyTyped()
*/
public String getKeyTyped(){
return keyTyped.poll();
}
/**
* Returns the time of the key typed event.
* Returns the top element in the whenKeyTyped queue,
* null if queue is empty.
*
* The element returned is removed from the event queue. See also
* general principles of event handling in FullScreen
* above.
*
* For more information see
* Chapter 6: Getting observer response of the
* Guide to Psychophysics programming with Java.
*
* @return the time of key typed event
*
* @see #getKeyTyped(long)
* @see #flushKeyTyped()
*/
public Long getWhenKeyTyped(){
return whenKeyTyped.poll();
}
/**
* Clears both keyTyped and whenKeyTyped queues. See also
* general principles of event handling in FullScreen
* above.
*
* For more information see
* Chapter 6: Getting observer response of the
* Guide to Psychophysics programming with Java.
*
* @see #getKeyTyped(long)
* @see #getWhenKeyTyped()
*/
public void flushKeyTyped(){
keyTyped.clear();
whenKeyTyped.clear();
}
/**
* Returns the key pressed by the observer.
*
* If the specified wait time is
* positive: This method either (i) returns the top element in the
* keyPressed queue immediately if there is
* at least one element in the keyPressed event queue or
* (ii) waits up to the specified amount of time for an element
* to become available. If no key is pressed
* within the specified amount of time
* it returns null.
*
* If the specified time is zero: Returns the top element in
* the keyPressed event queue or null if queue is empty.
*
* If the specified wait time is negative: This method
* either returns the top element in the queue or if the queue is
* empty it waits indefinetely untill the observer presses a key.
*
* In all cases, the element returned is removed from the event queue.
* See also
* general principles of event handling in FullScreen
* above.
*
* For more information see
* Chapter 6: Getting observer response of the
* Guide to Psychophysics programming with Java.
*
* @param ms time in milliseconds to wait for a response
*
* @return numeric code of the key pressed
*
* @see #getKeyPressed()
* @see #flushKeyPressed
* @see #getWhenKeyPressed()
*/
public Integer getKeyPressed(long ms){
Integer c = null;
try {
if(ms < 0)
c = keyPressed.take();
else
c = keyPressed.poll(ms, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
e.printStackTrace();
}
return c;
}
/**
* Returns the key pressed by the observer.
* Returns the top element in keyPressed
* queue or null if queue is empty. Equivelent to invoking getKeyPressed(0).
*
* The element returned is removed from the event queue. See also
* general principles of event handling in FullScreen
* above.
*
* For more information see
* Chapter 6: Getting observer response of the
* Guide to Psychophysics programming with Java.
*
* @return numeric code of the key pressed
*
* @see #getKeyPressed(long)
* @see #flushKeyPressed()
* @see #getWhenKeyPressed()
*/
public Integer getKeyPressed(){
return keyPressed.poll();
}
/**
* Returns the time of the key pressed event.
* Returns the top element in the whenKeyPressed queue,
* null if queue is empty.
*
* The element returned is removed from the event queue. See also
* general principles of event handling in FullScreen
* above.
*
* For more information see
* Chapter 6: Getting observer response of the
* Guide to Psychophysics programming with Java.
*
* @return the time of key pressed event
*
* @see #getKeyPressed(long)
* @see #flushKeyPressed()
*/
public Long getWhenKeyPressed(){
return whenKeyPressed.poll();
}
/**
* Clears both key pressed and when key pressed queues.
* See also
* general principles of event handling in FullScreen
* above.
*
* For more information see
* Chapter 6: Getting observer response of the
* Guide to Psychophysics programming with Java.
*
* @see #getKeyPressed(long)
* @see #getWhenKeyPressed()
*/
public void flushKeyPressed(){
keyPressed.clear();
whenKeyPressed.clear();
}
/**
* Returns the key released by the observer.
*
* If the specified wait time is
* positive: This method either (i) returns the top element in the
* keyReleased queue immediately if there is
* at least one element in the keyReleased event queue or
* (ii) waits up to the specified amount of time for an element
* to become available. If no key is released
* within the specified amount of time
* it returns null.
*
* If the specified time is zero: Returns the top element in
* the keyReleased event queue or null if queue is empty.
*
* If the specified wait time is negative: This method
* either returns the top element in the queue or if the queue is
* empty it waits indefinetely untill the observer releases a key.
*
* In all cases, the element returned is removed from the event queue.
* See also
* general principles of event handling in FullScreen
* above.
*
* For more information see
* Chapter 6: Getting observer response of the
* Guide to Psychophysics programming with Java.
*
* @param ms time in milliseconds to wait for a response
*
* @return numerical code of the key released
*
* @see #getKeyReleased()
* @see #flushKeyReleased
* @see #getWhenKeyReleased()
*/
public Integer getKeyReleased(long ms){
Integer c = null;
try {
if(ms < 0 )
c = keyReleased.take();
else
c = keyReleased.poll(ms, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
e.printStackTrace();
}
return c;
}
/**
* Returns the key released by the observer.
* Returns the top element in keyReleased
* queue or null if queue is empty. Equivelent to invoking getKeyReleased(0).
*
* The element returned is removed from the event queue. See also
* general principles of event handling in FullScreen
* above.
*
* For more information see
* Chapter 6: Getting observer response of the
* Guide to Psychophysics programming with Java.
*
* @return numerical code of the key released
*
* @see #getKeyReleased(long)
* @see #flushKeyReleased()
* @see #getWhenKeyReleased()
*/
public Integer getKeyReleased(){
return keyReleased.poll();
}
/**
* Returns the time of the key released event.
* Returns the top element in the whenKeyReleased queue,
* null if queue is empty.
*
* The element returned is removed from the event queue. See also
* general principles of event handling in FullScreen
* above.
*
* For more information see
* Chapter 6: Getting observer response of the
* Guide to Psychophysics programming with Java.
*
* @return the time of key released event
*
* @see #getKeyReleased(long)
* @see #flushKeyReleased()
*/
public Long getWhenKeyReleased(){
return whenKeyReleased.poll();
}
/**
* Clears both key released and when key released queues.
* See also
* general principles of event handling in FullScreen
* above.
*
* For more information see
* Chapter 6: Getting observer response of the
* Guide to Psychophysics programming with Java.
*
* @see #getKeyReleased(long)
* @see #getWhenKeyReleased()
*/
public void flushKeyReleased(){
keyReleased.clear();
whenKeyReleased.clear();
}
}
* It is possible to create Java objects from within Matlab and Mathematica. You
* can, therefore, create a NormalWindow object (and a JFrame)
* in Matlab or Mathematica and
* invoke its methods. In other words, if you choose, you could use this class
* as a tool for psychophysics programming much like the well known Psychtoolbox
* package.
*
* Instructions on Matlab and Mathematica development can be found here.
*
* @see FullScreen
* @author boyaci
*
*/
public class NormalWindow extends JPanel implements KeyListener{
private static final GraphicsEnvironment gEnvironment = GraphicsEnvironment
.getLocalGraphicsEnvironment();
private static final GraphicsDevice[] gDevices = gEnvironment
.getScreenDevices();
private GraphicsDevice gDevice;
private GraphicsConfiguration gConfiguration;
private final static int defaultNBuffers = 2;
private final static Color defaultBgColor = Color.BLACK;
private final static Color defaultFgColor = Color.LIGHT_GRAY;
private Color bgColor;
private final Font defaultFont = new Font("SansSerif", Font.BOLD, 36);
private BlockingQueue