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: *
* 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 keyTyped; private BlockingQueue whenKeyTyped; private BlockingQueue keyPressed; private BlockingQueue whenKeyPressed; private BlockingQueue keyReleased; private BlockingQueue whenKeyReleased; private BufferedImage screenImage; private final boolean defaultPassiveRendering = true; private boolean passiveRendering; public void keyTyped(KeyEvent ke) { keyTyped.offer(String.valueOf(ke.getKeyChar())); whenKeyTyped.offer(ke.getWhen()); } public void keyReleased(KeyEvent ke) { keyReleased.offer(ke.getKeyCode()); whenKeyReleased.offer(ke.getWhen()); } public void keyPressed(KeyEvent ke) { if (ke.getKeyCode() == KeyEvent.VK_ESCAPE) { System.exit(0); } else { keyPressed.offer(ke.getKeyCode()); whenKeyPressed.offer(ke.getWhen()); } } /** * Constructs a NormalWindow object on the screen with displayID * * @param displayID */ public NormalWindow(int displayID){ super(); gDevice = gDevices[displayID]; gConfiguration = gDevice.getDefaultConfiguration(); try { setFocusable(true); setPassiveRendering(defaultPassiveRendering); setBackground(defaultBgColor); super.setBackground(defaultBgColor); setFont(defaultFont); setForeground(defaultFgColor); setNBuffers(defaultNBuffers); keyTyped = new LinkedBlockingQueue(); whenKeyTyped = new LinkedBlockingQueue(); keyPressed = new LinkedBlockingQueue(); whenKeyPressed = new LinkedBlockingQueue(); keyReleased = new LinkedBlockingQueue(); whenKeyReleased = new LinkedBlockingQueue(); addKeyListener(this); } finally {} } /** * Constructs a new NormalWindow object. * */ public NormalWindow(){ this(0); } /** * By default NormalWindow uses Double Buffering. * Set the number to 1 for no double buffering * * @param n */ public void setNBuffers(int n) { if (n>1) setDoubleBuffered(true); else setDoubleBuffered(false); } public int getNBuffers() { if(isDoubleBuffered()) return 2; else return 1; } private void createScreenImage() { if(screenImage == null || screenImage.getWidth() != getWidth() || screenImage.getHeight() != getHeight() ) screenImage = gConfiguration.createCompatibleImage(getWidth(),getHeight()); } /** * Sets passive rendering parameter. * 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. *

* 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(); } }