Site logo, www.grelf.net
 

Dark-adapted software

 

There are now many computer applications that are really useful at the telescope but there is a problem with computer screen brightness. It cannot be turned down to anything like a level at which the dark-adaptation of the observer's eyes is unaffected.

There are several possible approaches to solving the problem. Making the screen red helps. You might

I have noticed that the first of these is a requirement at star parties advertised on the Internet.

(A further tip: stick black tape over that "cool" but irritatingly bright LED logo on your laptop case. And probably also over those white or blue LEDs that show disc activity, caps lock status, etc. My laptop is a patchwork of black tape.)

None of the approaches listed above is ideal because

As a Java programmer I write my own tools to use on my laptop at the telescope. I have worked out how to make everything red on black, including window borders. Being an unusual requirement, the details of how to do this are not well documented. It is not covered by Oracle's (formerly Sun's) Java Tutorial. So I thought I would share my findings.

One of the nice features of Java's swing graphics package is called Pluggable Look And Feel (PLAF). To make an application look right in whatever system it is running on, the following is needed as one of the first executable statements (unless otherwise stated, classes in my sample code are in the standard package javax.swing).


	UIManager.setLookAndFeel (UIManager.getSystemLookAndFeelClassName ());
		

In theory PLAF enables completely different window styles to be used in an application. It is possible to create a new look and feel from scratch or, more practically, by subclassing


	javax.swing.plaf.basic.BasicLookAndFeel
		

and how to do that is described, up to a point, in some books (eg, Matthew Robinson, Pavel Vorobiev: Swing, Second Edition, Manning, ISBN 1-930110-88-X - the relevant Chapter 21 of this book can be found online by searching). However, I found this approach quite complicated and, as far as I went with it, it seemed to upset the behaviour of user interaction with tables (javax.swing.JTable). So I tried a different approach, with complete success.

The Metal Look & Feel is one of the standard PLAFs supplied with Java. It is also known as the Java Look & Feel because it is what you get if you do not set any particular look and feel with the UIManager statement I showed above. Metal has "themability" and this proved to be an easier way to achieve my objective.

It is necessary to subclass javax.swing.plaf.metal.MetalTheme - I wrote org.britastro.grelf.ObservingTheme in which I overrode a number of extremely simple methods that return colour and font resource objects to the system (JVM). The full code of ObservingTheme is given at the end of this article. Then the following statements at the start of the main program create the required look and feel.


	UIManager.setLookAndFeel ("javax.swing.plaf.metal.MetalLookAndFeel");
	MetalLookAndFeel.setCurrentTheme (new ObservingTheme ());
	UIManager.setLookAndFeel (new MetalLookAndFeel ());
		

That somewhat illogical-looking code successfully changes all buttons, text fields, drop-downs, text labels, background panels, etc, but NOT the borders and caption bars of windows and dialogues because those are normally the responsibility of the operating system rather than the application. However, the Java Look & Feel does allow control of those too.

Window borders and caption bars comprise the "decoration" of the window. There are two ways to prevent them spoiling our attempt at dark-adapted software.

The first way is to declare windows and dialogues to be undecorated, just after construction but before they are displayed:


	JFrame frame = new JFrame ();
	frame.setUndecorated (true);
		

That works but without borders or a caption bar the window can never be resized or dragged around by the user. That is generally not very clever but it is appropriate for a background main window that deliberately fills the whole screen to blot out the light from the desk-top.

So a better way for sub-windows is to add a further statement:


	JFrame frame = new JFrame ("Caption - will be seen now");
	frame.setUndecorated (true);
	frame.getRootPane().setWindowDecorationStyle (JRootPane.FRAME);
		

That magically gives the JFrame (or JDialog, or whatever) basic but functional decorations in the required red style. Here is an example from one of my observer-friendly applications:

The background is the screen-filling main window of the application. A temporary window is over that, presenting a "Set sky point" dialogue to the user.

(I use this application for finding faint objects very effectively from a naked eye starting point. It shows me a view similar to what I see in my 254mm aperture Newtonian on low power, rotatable to match my view. It shows me in which direction to move to the target and how far I have got still to go. I do not have a "go to" mount, so this program saves me an enormous amount of time.
More about Hopper.)

Finally, here is the simple subclass that I had to write:


package org.britastro.grelf.plaf;

// Author: Graham Relf (www.grelf.net) 2012

import java.awt.Color;
import java.awt.Font;
import javax.swing.plaf.ColorUIResource;
import javax.swing.plaf.FontUIResource;
import javax.swing.plaf.metal.MetalTheme;

/** Theme that uses only red and black, for minimal effect on dark-adapted astronomers' eyes.
  * For use with Swing's MetalLookAndFeel. */
public class ObservingTheme extends MetalTheme
{
  private static final FontUIResource FONT = new FontUIResource ("Arial", Font.BOLD, 18);
  private static final ColorUIResource LIGHT_RED = new ColorUIResource (new Color (255, 0, 0));
  private static final ColorUIResource RED = new ColorUIResource (new Color (191, 0, 0));
  private static final ColorUIResource DARK_RED = new ColorUIResource (new Color (127, 0, 0));
  private static final ColorUIResource VERY_DARK_RED = new ColorUIResource (new Color (91, 0, 0));
  private static final ColorUIResource BLACK = new ColorUIResource (Color.BLACK);

  @Override public String getName () { return "ObservingTheme"; }

  @Override protected ColorUIResource getBlack () { return BLACK; }
  @Override public    ColorUIResource getControl () { return getSecondary3 (); }
  @Override public    ColorUIResource getControlDarkShadow () { return VERY_DARK_RED; }
  @Override public    ColorUIResource getControlDisabled () { return DARK_RED; }
  @Override public    ColorUIResource getControlHighlight () { return LIGHT_RED; }
  @Override public    ColorUIResource getControlInfo () { return BLACK; }
  @Override public    ColorUIResource getControlShadow () { return DARK_RED; }
  @Override public    ColorUIResource getControlTextColor () { return getControlInfo (); }
  @Override public    ColorUIResource getDesktopColor () { return VERY_DARK_RED; }
  @Override public    ColorUIResource getFocusColor () { return getPrimary2 (); }
  @Override public    ColorUIResource getHighlightedTextColor () { return BLACK; }
  @Override public    ColorUIResource getInactiveControlTextColor () 
                                                            { return getControlDisabled (); }
  @Override public    ColorUIResource getInactiveSystemTextColor () { return VERY_DARK_RED; }
  @Override public    ColorUIResource getPrimaryControl () { return RED; }
  @Override public    ColorUIResource getPrimaryControlShadow () { return DARK_RED; }
  @Override public    ColorUIResource getPrimaryControlDarkShadow () { return VERY_DARK_RED; }
  @Override public    ColorUIResource getPrimaryControlInfo () { return BLACK; }
  @Override public    ColorUIResource getPrimaryControlHighlight () { return LIGHT_RED; }
  @Override protected ColorUIResource getPrimary1 () { return RED; }
  @Override protected ColorUIResource getPrimary2 () { return RED; }
  @Override protected ColorUIResource getPrimary3 () { return RED; }
  @Override protected ColorUIResource getSecondary1 () { return DARK_RED; }
  @Override protected ColorUIResource getSecondary2 () { return DARK_RED; }
  @Override protected ColorUIResource getSecondary3 () { return DARK_RED; }
  @Override public    ColorUIResource getSystemTextColor () { return BLACK; }
  @Override public    ColorUIResource getTextHighlightColor () { return LIGHT_RED; }
  @Override public    ColorUIResource getUserTextColor () { return BLACK; }
  @Override protected ColorUIResource getWhite () { return LIGHT_RED; }
  @Override public    ColorUIResource getWindowBackground () { return RED; }
  @Override public    ColorUIResource getWindowTitleBackground () { return getPrimary3 (); }
  @Override public    ColorUIResource getWindowTitleForeground () { return getBlack (); }
  @Override public    ColorUIResource getWindowTitleInactiveBackground () 
                                      { return getSecondary3 (); }
  @Override public    ColorUIResource getWindowTitleInactiveForeground() { return VERY_DARK_RED; }

  @Override public FontUIResource getControlTextFont () { return FONT; }
  @Override public FontUIResource getMenuTextFont () { return FONT; }
  @Override public FontUIResource getSubTextFont () { return FONT; }
  @Override public FontUIResource getSystemTextFont () { return FONT; }
  @Override public FontUIResource getUserTextFont () { return FONT; }
  @Override public FontUIResource getWindowTitleFont () { return FONT; }

} // ObservingTheme
		

It is not clear in the code of MetalTheme what Primary1, etc, are used for. To really check that it would be necessary to examine Oracle's source code for many other classes. That is readily available but I found I could do what I needed without going that far.

Next page