2008-11-28

Theme a site with Greasemonkey and Stylish

Motivated by Gmail's Terminal theme, I hacked a similar theme for an internal Web site using Firefox's Greasemonkey and Stylish add-ins. My approach was to write some small Greasemonkey scripts to touch up different parts of all pages independently, then use Stylish to apply style definitions (the theme) from one CSS file. If you want to create another theme for this site, just create a new CSS file.

  1. First, I identified logical components of each web page, for example navigation bar, header, footers, forms and tables.
  2. Wrote a Greasemonkey script for each logical component that:
    1. Removed any physical formatting such as border or bgcolor.
    2. Labelled each component with an ID and / or a class name.
  3. When naming each script, prefixed each script name with the site name to make them easy to find in Greasemonkey's Manage User Scripts dialog.
  4. Configured Greasemonkey's Included and Excluded pages to call each script as required.
  5. Wrote a site-wide CSS file, using the IDs and class names that were previously defined.
  6. Added the CSS file to Stylish. The CSS file name is prefixed with the site and theme name.

Instant theme!

See Also

2008-11-20

Foxit PDF Reader Page Navigation

The Foxit PDF reader lets you can turn to the next page using Space or Right, or to previous page using Shift+Space, Left or Backspace shortcut keys. If you scroll a page, the page is moved up or down by the height of the view, so the next or previous page is always slightly shifted vertically in the view because the view is not the same height as the page. After scolling several pages, you have to adjust the view to display the whole of a page again. When reading paginated documents, it's easier to turn pages instead of scrolling through pages.

Foxit developers: The function names in italics are swapped in the on-line help.

Note: Maria H. reports that the same keys also work for the Adobe PDF Reader.

2008-11-19

Accelerate and Brake Moderately

There's many ideas to save petrol, but which is the most effective? As a starting point, my Magna TS consumed 16L/100km of petrol in city driving. After some months of experimentation with different techniques, I found that the most effective method was (taa-daa!) … to accelerate and brake moderately. Many other techniques (e.g. leaving appropriate space behind the vehicle in front, drive smoothly) are a consequence of this one. Now, my fuel consumption is down to 12L/100km.

Of course, your mileage may vary!

2008-11-08

Simple Clock Custom Control using Swing and Windows.Forms

This article describes how to create a simple analogue clock using Java Swing and .Net Windows.Forms, and it will cover creating a custom control, simple 2D drawing, updating the display regularly with a timer. In addition, the control can display the time in a user-selectable time zone and is resizable.

To give you an idea of the goal, below are images of the Swing and Windows.Forms control, respectively, in a test application:

Clock Control Java Clock Control .Net

Define Custom Control

The custom control has to draw a clock face and hands. We specify the height and width of the clock face and use a high-quality drawing mode to smooth out the lines and avoid jaggies. By default, the control will display the time in the current time zone.

Java Swing

Sub-class JComponent and override the paintComponent() method:

public class ClockPanel extends JPanel {
  private double cx, cy, diameter, radius;
  private final double RADIAN = 180.0 / Math.PI;
  private TimeZone timeZone = null;

  public void paintComponent(Graphics g) {
    super.paintComponent(g);
    Graphics2D g2 = (Graphics2D) g;
    g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    paintFace(g2);
    paintHands(g2);
  }
}

Windows.Forms

Sub-class Control and override OnPaint() method:

  public partial class ClockControl : Control {
    private double cx, cy, diameter, radius;
    private const double RADIAN = 180.0 / Math.PI;
    private TimeZoneInfo tzi = TimeZoneInfo.Local;

    protected override void OnPaint(PaintEventArgs e) {
      base.OnPaint(e);
      Graphics g = e.Graphics;
      g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
      PaintFace(g);
      PaintHands(g);
    }
  }

2D Drawing

The clock is drawn in two steps: first a circular clock face is drawn, then the hands are drawn on top of the face. The face is circular, so the minimum of the height and width of the drawable area is used.

Clock Face

To drawing the clock face's numbers, we convert each number into string, then an image, compute that image's mid-point and place it numbers using that mid-point. If we don't use the mid-point, then each number will be placed too high and on the right of the tip of the clock hands.

Java Swing
  private void paintFace(Graphics2D g2) {
    g2.setPaint(Color.GRAY);
    g2.fill(new Ellipse2D.Double(0, 0, diameter, diameter));
    g2.setPaint(Color.LIGHT_GRAY);
    g2.fill(new Ellipse2D.Double(20, 20, diameter - 40, diameter - 40));
    g2.setPaint(Color.BLACK);
    for (int i = 1; i <= 12; i++) {
      TextLayout tl = new TextLayout(Integer.toString(i), getFont(), g2.getFontRenderContext());
      Rectangle2D bb = tl.getBounds();
      double angle = (i * 30 - 90) / RADIAN;
      double x = (radius - 10) * Math.cos(angle) - bb.getCenterX() / 2;
      double y = (radius - 10) * Math.sin(angle) - bb.getCenterY() / 2;
      tl.draw(g2, (float)(cx + x), (float)(cy + y));
    }
  }
Windows.Forms
    private void PaintFace(Graphics g) {
      SolidBrush brush = new SolidBrush(Color.DarkGray);
      g.FillEllipse(brush, 0, 0, (float)diameter, (float)diameter);
      brush.Color = Color.LightGray;
      g.FillEllipse(brush, 20, 20, (float)(diameter - 40), (float)(diameter - 40));
      brush.Color = Color.Black;
      for (int i = 1; i <= 12; i++) {
        string num = Convert.ToString(i);
        Size size = TextRenderer.MeasureText(g, num, Font, new Size(int.MaxValue, int.MaxValue), TextFormatFlags.NoPadding);
        double angle = (i * 30 - 90) / RADIAN;
        double x = (radius - 10) * Math.Cos(angle) - size.Width/2;
        double y = (radius - 10) * Math.Sin(angle) - size.Height/2;
        g.DrawString(num, Font, brush, (float)(cx+x), (float)(cy+y));
      }
      brush.Dispose();
    }

Calculate Hand Positions

For each tick, we calculate the position of the hour, minute and second hands based on the current time and time zone. The hour hand is moved slightly forward each minute, and the minute hand is moved slightly forward each second, so that they don't jump at the start of the next hour and minute, respectively.

Java Swing
  private void paintHands(Graphics2D g2) {
    Calendar now = Calendar.getInstance(timeZone);
    int hour = now.get(Calendar.HOUR_OF_DAY);
    int minute = now.get(Calendar.MINUTE);
    int second = now.get(Calendar.SECOND);
    double angle = 0.0;
    
    angle = (hour % 12 * 30 + minute / 2) / RADIAN;
    paintHand(g2, 0.4, angle, 6, Color.YELLOW);
    
    angle = (minute * 6 + second / 10) / RADIAN;
    paintHand(g2, 0.6, angle, 4, Color.BLUE);
    
    angle = second * 6 / RADIAN;
    paintHand(g2, 0.8, angle, 2, Color.RED);
  }
Windows.Forms
    private void PaintHands(Graphics g) {
      DateTime dt = DateTime.UtcNow + tzi.GetUtcOffset(DateTimeOffset.UtcNow);
      double angle = 0.0;

      angle = (dt.Hour % 12 * 30 + dt.Minute / 2) / RADIAN;
      PaintHand(g, 0.4, angle, 6f, Color.Yellow);

      angle = (dt.Minute * 6 + dt.Second / 10) / RADIAN;
      PaintHand(g, 0.6, angle, 4f, Color.Blue);

      angle = dt.Second * 6 / RADIAN;
      PaintHand(g, 0.8, angle, 2f, Color.Red);
    }

Draw Hands

Now, we draw each clock hand.

Java Swing
  private void paintHand(Graphics2D g2, double proportion, double angle, float width, Color color) {
    double x = radius * proportion * Math.sin(angle);
    double y = -radius * proportion * Math.cos(angle);
    g2.setPaint(color);
    g2.setStroke(new BasicStroke(width, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
    g2.draw(new Line2D.Double(cx, cy, cx + x, cy + y));
  }
Windows.Forms
    private void PaintHand(Graphics g, double proportion, double angle, float width, Color color) {
      double x = radius * proportion * Math.Sin(angle);
      double y = -radius * proportion * Math.Cos(angle);
      Pen pen = new Pen(color, width);
      pen.EndCap = System.Drawing.Drawing2D.LineCap.Round;
      g.DrawLine(pen, (float)cx, (float)cy, (float)(cx + x), (float)(cy + y));
      pen.Dispose();
    }

Timer

The clock control updates itself every second using a timer. We set up the timer in the control's constructor.

Java Swing

Each 1000 ms, the timer will add a repaint request to the Swing event queue, which eventually results in calling the object's paintComponent() method.

  public ClockPanel() {
    super();
    ...  
    setTimeZone(TimeZone.getDefault());
    Timer t = new Timer(1000, new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        repaint();
      }
    });
    t.start();
  }

Windows.Forms

Each 1000 ms, the timer will call the timer_Tick() function, which in turn invalidates the clock control so that Windows.Forms will generate a OnPaint() event (that's how I think it works).

    public ClockControl() {
      CalculateSize(); // Control's size is available at this point.
      Timer timer = new System.Windows.Forms.Timer();
      timer.Enabled = true;
      timer.Interval = 1000;
      timer.Tick += new EventHandler(timer_Tick);
    }

    void timer_Tick(object sender, EventArgs e) {
      Invalidate();
    }

Time Zones

For interest, the clock control displays the time is a specified time zone, so we provide methods for an external caller to get and set the control's time zone.

Java Swing

  public TimeZone getTimeZone() { return timeZone; }
  public void setTimeZone(TimeZone tz) { timeZone = tz; }

Windows.Forms

    public TimeZoneInfo TZI {
      get { return tzi; }
      set { tzi = value; }
    }

Test Code

To test the clock control, create a test application.

NetBeans

  1. Using NetBeans, create a new application sample form called SimpleClockView.
  2. Drag the clock's source code icon from the Projects window into SimpleClockView's Design pane.
  3. Drag a JComboBox, called timeZoneList into the Design pane.
  4. Add the following action handler into timeZoneList to change the clock's time zone when the user selects a new time zone:
      private void timeZoneListActionPerformed(java.awt.event.ActionEvent evt) {
        // TODO add your handling code here:
        String tzID = (String) this.timeZoneList.getSelectedItem();
        clockPanel1.setTimeZone(TimeZone.getTimeZone(tzID));
      }                                            
    
  5. Initialize timeZoneList with a list of time zones in SimpleClockView's constructor:
    public class SimpleClockView extends FrameView {
    
        public SimpleClockView(SingleFrameApplication app) {
            super(app);
    
            initComponents();
            String[] sortedTzID = TimeZone.getAvailableIDs();
            Arrays.sort(sortedTzID);
            timeZoneList.setModel(new javax.swing.DefaultComboBoxModel(sortedTzID));
        ...
    

SharpDevelop

  1. Using SharpDevelop, create a new Windows Applications Form called Form1.
  2. Drag your new clock control from the Toolbox into the Form1's Designer pane.
  3. Drag a ListBox, called timeZoneList into the Design pane.
  4. Bind timeZoneList's SelectedIndexChange event to the timeZoneList_SelectedIndexChange() function to change the clock's time zone when the user selects a new time zone:
        private void timeZoneList_SelectedIndexChanged(object sender, EventArgs e) {
          String tzId = this.timeZoneList.SelectedValue as String;
          if (tzId != null) this.clockControl1.TZI = TimeZoneInfo.FindSystemTimeZoneById(tzId);
        }
    
  5. Initialize timeZoneList with a list of time zones in Form1's constructor:
    namespace ClockNS {
      public partial class Form1 : Form {
        public Form1() {
          InitializeComponent();
          this.timeZoneList.DataSource = TimeZoneInfo.GetSystemTimeZones();
          this.timeZoneList.DisplayMember = "DisplayName";
          this.timeZoneList.SelectedValue = "Id";
        }
      ...
      }
    }
    

Handling Resizing

A nice additional feature is for the clock control to resize itself when the test application's is resized.

Java Swing

Add a componentResized handler to recalculate the component's size and request Swing to repaint the component. Your component may have a small inset within its boundary, so you should take that into account when calculating the clock's size.

  public ClockPanel() {
    super();
  
    addComponentListener(new ComponentAdapter() {
      @Override
      public void componentResized(ComponentEvent e) {
        calculateSize();
        repaint();
      }
    });
    ...
  }

  private void calculateSize() {
    Insets insets = getInsets();
    int width = getWidth() - insets.left - insets.right;
    int height = getHeight() - insets.top - insets.bottom;
    diameter = Math.min(width, height);
    cx = cy = radius = diameter / 2;
  }

Windows.Forms

Override the base class' OnResize() function to recalculate the clock's size and redraw the control.

    protected override void OnResize(EventArgs e) {
      base.OnResize(e);
      CalculateSize();
      Invalidate();
    }

    private void CalculateSize() {
      diameter = Math.Min(this.Width, this.Height) - 2;
      cx = cy = radius = diameter / 2;
    }

Conclusion

I've described how to create a custom control in Java Swing + NetBeans and .Net Windows.Forms + SharpDevelop using (nearly) the same procedure and structure. I experiment with both environments because a feature in one motivates me to find an equivalent feature in the other and to synthesize a common (and hopefully better) solution.

See Also