Number Converter

NumberConverter.png

Screenshot

Convert numbers between binary, octal, decimal and hexadecimal.

/*
 * Copyright (c) 2002, 2003, 2009 Jochen Kupperschmidt
 * Released under the terms of the MIT License
 *    _                               _
 *   | |_ ___ _____ ___ _ _ _ ___ ___| |_
 *   |   | . |     | ._| | | | . |  _| . /
 *   |_|_|___|_|_|_|___|_____|___|_| |_|_\
 *     http://homework.nwsnet.de/
 *
 * Convert numbers between binary, octal, decimal and hexadecimal.
 *
 * This is a Groovy remake (24-Dec-2009) of the original Java application
 * (12-Jan-2003) and requires significantly less code.
 */

import groovy.swing.SwingBuilder
import java.awt.GridLayout
import javax.swing.ButtonGroup
import javax.swing.JFrame
import javax.swing.JPanel
import javax.swing.JRadioButton
import javax.swing.JTextField
import javax.swing.text.AbstractDocument
import javax.swing.text.AttributeSet
import javax.swing.text.BadLocationException
import javax.swing.text.PlainDocument

enum NumberSystem {

  BINARY(2, '01', { Integer.toBinaryString(it) }),
  OCTAL(8, '01234567', { Integer.toOctalString(it) }),
  DECIMAL(10, '0123456789', { Integer.toString(it) }),
  HEXADECIMAL(16, '0123456789abcdefABCDEF',
      { Integer.toHexString(it).toUpperCase() })

  final int base
  final Set<String> validCharacters
  final Closure toStringValue

  NumberSystem(int base, String validCharacters, Closure toStringValue) {
    this.base = base
    this.validCharacters = new HashSet<String>(validCharacters.collect { it })
    this.toStringValue = toStringValue
  }
}

class NumberDocument extends PlainDocument {

  private Set<String> validCharacters
  private Closure actionHandler

  def NumberDocument(Set<String> validCharacters, Closure actionHandler) {
    this.validCharacters = validCharacters
    this.actionHandler = actionHandler
  }

  // Fire action on text insertion.
  public void insertUpdate(AbstractDocument.DefaultDocumentEvent event,
      AttributeSet attrSet) {
    actionHandler()
  }

  // Fire action on text removal.
  public void postRemoveUpdate(AbstractDocument.DefaultDocumentEvent event) {
    actionHandler()
  }

  // Validate the entered characters.
  public void insertString(int offset, String str, AttributeSet attrSet)
      throws BadLocationException {
    super.insertString(offset,
        str.findAll { validCharacters.contains(it) }.join(''), attrSet)
  }
}

/**
 * A panel containing a labeled radio button and a special text field.
 */
class NumberPanel extends JPanel {

  NumberSystem system
  JRadioButton radioButton
  JTextField field

  def NumberPanel(NumberSystem system, boolean selected) {
    this.system = system
    layout = new GridLayout(0, 2)

    // Create a radio button.
    radioButton = new JRadioButton(selected: selected,
        text: system.name()[0] + system.name()[1..-1].toLowerCase() + ':')
    add(radioButton)

    // Create a text field that only accepts specified characters.
    field = new JTextField(document: new NumberDocument(system.validCharacters,
        { field.fireActionPerformed() }), enabled: selected,
        horizontalAlignment: JTextField.RIGHT)
    add(field)
  }

  def void changeInputSystem() {
    field.enabled = radioButton.selected
  }

  def void clear() {
    field.text = ''
  }

  def boolean isEmpty() {
    field.text.trim().isEmpty()
  }

  def int getValue() throws NumberFormatException {
    Integer.valueOf(field.text.trim(), system.base)
  }

  def void setValue(int value) {
    field.text = system.toStringValue(value)
  }
}

class NumberPanelGroup {

  def panels = []
  ButtonGroup radioButtonGroup = new ButtonGroup()
  boolean locked = false

  def NumberPanelGroup(NumberSystem selectedSystem) {
    NumberSystem.values().each {system ->
      def numberPanel = new NumberPanel(system, system == selectedSystem)
      radioButtonGroup.add(numberPanel.radioButton)
      numberPanel.radioButton.actionPerformed = { panels*.changeInputSystem() }
      numberPanel.field.actionPerformed = {
        // Locking is needed since it would cause another
        // action event otherwise, resulting in a loop.
        if (locked) {
          return
        }
        locked = true
        try {
          if (!numberPanel.isEmpty()) {
            // Re-calculate values.
            panels*.value = numberPanel.value
          } else {
            // Clear all fields.
            panels*.clear()
          }
        } finally {
          locked = false
        }
      }
      panels << numberPanel
    }
  }
}

// Program starts here.
def swing = new SwingBuilder()
swing.lookAndFeel('gtk')
swing.frame(defaultCloseOperation: JFrame.EXIT_ON_CLOSE,
    locationRelativeTo: null, pack: true, resizable: false, show: true,
    title: 'Number Converter') {
  panel(border: compoundBorder(emptyBorder(4),
      compoundBorder(etchedBorder(), emptyBorder(2)))) {
    gridLayout(cols: 1, rows: 0)

    new NumberPanelGroup(NumberSystem.DECIMAL).panels.each { widget(it) }
  }
}