/*
* [InWords.java]
*
* Summary: displays numbers in words in various languages.
*
* Copyright: (c) 1999-2017 Roedy Green, Canadian Mind Products, http://mindprod.com
*
* Licence: This software may be copied and used freely for any purpose but military.
* http://mindprod.com/contact/nonmil.html
*
* Requires: JDK 1.8+
*
* Created with: JetBrains IntelliJ IDEA IDE http://www.jetbrains.com/idea/
*
* Version History:
* 1.0 1999-01-12 initial version
* 1.1 1999-01-13 add Norwegian, Esperanto, British English.
* 1.2 1999-01-13 correct millionoj bug in Esperanto version.
* 1.3 1999-01-14 add Bahasa Indonesia
* 1.4 1999-01-14 add Swedish
* 1.5 1999-01-15 languages now read from com.mindprod.inwords.Inwords.properties file
* rather than being hard-coded into the InWords program.
* add Dutch - minor touchups to other languages.
* 1.6 1999-01-16 corrections to British English and Dutch.
* don't load language class until used.
* 1.7 1999-01-17 add hex, octal, binary, and Martian.
* 1.8 1999-01-18 add Icelandic
* remove some redundant remr tests.
* 1.9 1999-01-18 table driven test
* add French
* 2.0 1999-01-18 make refresh protected so can call from inner class without
* upsetting JDK 1.2 class verifier
* corrections to French
* corrections to Dutch
* 2.1 1999-01-18 add German
* 2.2 1999-01-18 add old Dutch and Banker's Dutch
* 2.3 1999-01-19 add Roman numerals
* minor corrections to German
* 2.4 1999-01-21 add Polish
* 2.5 1999-01-22 add American ordinals
* tidy classes so all language-specific Strings are constants rather
* than literals.
* 2.6 1999-02-04 corrections to Swedish and Icelandic
* 2.7 1999-02-05 add RAM byte measure and Metric gram measure.
* 2.8 1999-02-07 add Tagalog (Filipino)
* 2.9 1999-08-11 correct 8 bug in Roman numerals
* 3.0 1999-10-29 add Japanese
* 3.1 2000-01-01
* 3.2 2001-01-01
* 3.3 2002-01-01
* 3.4 2004-07-07 add triple plurals to Polish above 1 million.-
* add about box -new style gridbag
* 3.5 2005-09-27 Correct bug in display of negative nine digit numbers. -
* in DecimalDots and DecimalCommas
* 3.6 2005-12-16 add Javadoc
* 3.7 2006-10-11 add Swiss Vaudois French
* 3.8 2006-10-15 reformat with IntelliJ, add Javadoc.
* 3.9 2006-10-16 new French. rename DecimalSpaces
* 4.0 2007-05-24 add pad, icon, IntelliJ inspector.
* 4.1 2008-04-02 add build number to title
* 4.2 2009-01-20 correct many mistakes in Spanish
* 4.3 2009-01-21 fine tune Spanish and SpanishForCheques
* 4.4 2009-01-22 add Italian, JSpinner, convert static inWords to instance toWords.
* 4.5 2009-01-23 now uses font that will display Japanese
* 4.6 2009-05-01 corrections to Italian. Tidy package and class comments.
*/
/*
TODO:
echo back number in comma format cleaned up.
Right justify number user keys in.
use exceptions for numbers classes can't handle.
handle places to right of decimal.
*/
package com.mindprod.inwords;
import com.mindprod.common18.Build;
import com.mindprod.common18.CMPAboutJBox;
import com.mindprod.common18.Common18;
import com.mindprod.common18.FontFactory;
import com.mindprod.common18.HybridJ;
import com.mindprod.common18.JEButton;
import com.mindprod.common18.Misc;
import com.mindprod.common18.VersionCheck;
import javax.swing.JApplet;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JSpinner;
import javax.swing.JTextArea;
import javax.swing.SpinnerNumberModel;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.io.IOException;
import java.io.InputStream;
import static java.lang.System.*;
/**
* displays numbers in words in various languages.
*
* Teaches you how to count in various languages.
* Spells out numbers in words.
* Test driver for the various InWords classes.
*
* when you add a language, update the following places:
* inwords.properties
* inwords.use
* InWords.java classcomment
* inwords.xml PAD file.
*
* Inwords is the GUI
* ToWords is the interface the languge classes must implement
* French German etc are classes for each language
*
* Learn To Count shows you how to count in any of the
* following languages:
*
* Bahasa Indonesia
* Binary
* Decimal (several variants)
* Dutch (modern, old and banker's)
* English (British, North American, Ordinals)
* Esperanto
* French
* German
* Grams
* Hexadecimal
* Icelandic
* Japanese
* Martian
* Metric Metric Prefixes (grams)
* Norwegian
* Octal
* Polish
* RAM (bytes)
* Roman Numerals
* Spanish
* Swedish
* Swiss French
* Tagalog
* Time Intervals
*
* You can enter a number and it displays it in words: e.g.
* -12345 to "minus twelve thousand three hundred forty-five"
*
* The classes it uses for conversion can be easily
* cannibalised for your own applications.
*
* for example:
*
* String numStr = BritishEnglish.inWords(-1234);
*
* will generate the string:
*
* "minus one thousand two hundred four"
*
* handles longs up to Long.MAX_VALUE, namely:
* 9,223,372,036,854,775,807
*
* Useful to learn to count in a foreign language. The InWords
* package classes are also useful for cheque writing or legal
* documents.
*
*
* @author Roedy Green, Canadian Mind Products
* @version 4.6 2009-05-01 corrections to Italian. Tidy package and class comments.
* @since 1999-01-12
*/
@SuppressWarnings( { "FieldCanBeLocal" } )
public final class InWords extends JApplet
{
/**
* height of Applet box in pixels. Does not include surrounding frame.
*/
private static final int APPLET_HEIGHT = 276;
/**
* Width of Applet box in pixels.
*/
private static final int APPLET_WIDTH = 525;
private static final int FIRST_COPYRIGHT_YEAR = 1999;
/**
* undisplayed copyright notice
*
* @noinspection UnusedDeclaration
*/
private static final String EMBEDDED_COPYRIGHT =
"Copyright: (c) 1999-2017 Roedy Green, Canadian Mind Products, http://mindprod.com";
private static final String RELEASE_DATE = "2009-05-01";
private static final String TITLE_STRING = "InWords Amanuensis";
private static final String VERSION_STRING = "4.6";
private static final Color BACKGROUND_FOR_BODY = Color.WHITE;
// Use our own colours so Symantec won't mess with them or create dups.
private static final Color FOREGROUND_FOR_DISPLAY = Color.BLACK;
private static final Color FOREGROUND_FOR_INSTRUCTIONS = new Color( 0x008000 );
private static final Color FOREGROUND_FOR_LABEL = new Color( 0x0000b0 );
// --Commented out by Inspection (02/04/08 3:39 PM):private static final Color red = Color.RED;
/**
* for titles
*/
private static final Color FOREGROUND_FOR_TITLE = new Color( 0xdc143c );
/**
* for for titles and About buttons
*/
private static final Font FONT_FOR_TITLE = FontFactory.build( "Dialog", Font.BOLD, 16 );
/**
* for for title second line
*/
private static final Font FONT_FOR_TITLE2 = FontFactory.build( "Dialog", Font.PLAIN, 14 );
/**
* Delegate object that supports the toWords method for each language.
*/
private static ToWords[] languageDelegate;
/**
* list of languages supported, as they appear in the menu.
*/
private static String[] languageNames;
/**
* about box
*/
private JButton about;
/**
* which language you want number displayed in
*/
private JComboBox language;
/**
* instructions
*/
private JLabel instructions;
/**
* label for language
*/
private JLabel languageLabel;
/**
* label for the number
*/
private JLabel theNumberLabel;
/**
* title
*/
private JLabel title;
/**
* title, second line
*/
private JLabel title2;
/**
* spinner to define the number to display
*/
private JSpinner spinner;
/**
* the number in words
*/
private JTextArea display;
/**
* tracks current value of spinner
*/
private SpinnerNumberModel spinnerNumberModel;
/**
* Finds all the languages supported in the InWords.properties file. Creates languageNames and languageDelegate
* arrays.
*/
private static void findLanguages()
{
// The properties file looks like this:
//
//# com.mindprod.inwords.inwords.properties must live in inwords.jar.
//# Describes languages supported to translate numbers to words.
//# Fully qualified classname, (without .class)=name on menu (embedded blanks ok)
//# Everything is case-sensitive!!
//com.mindprod.inwords.Indonesian=Bahasa Indonesia
//com.mindprod.inwords.Binary=Binary
//com.mindprod.inwords.AmericanEnglish=English (North American)
//com.mindprod.inwords.AmericanOrdinals=English (ordinals)
//com.mindprod.inwords.BritishEnglish=English (British)
//com.mindprod.inwords.DecimalCommas=Decimal (commas)
//com.mindprod.inwords.DecimalDots=Decimal (dots)
//com.mindprod.inwords.DecimalSpaces=Decimal (spaces)
//com.mindprod.inwords.Dutch=Dutch (modern)
//com.mindprod.inwords.BankerDutch=Dutch (banker's)
//com.mindprod.inwords.OldDutch=Dutch (old)
//com.mindprod.inwords.Esperanto=Esperanto
//com.mindprod.inwords.French=French
//com.mindprod.inwords.German=German
//com.mindprod.inwords.Hex=Hexadecimal
//com.mindprod.inwords.Icelandic=Icelandic
//com.mindprod.inwords.Italian=Italian (beta)
//com.mindprod.inwords.Japanese=Japanese (requires Kanji Unicode)
//com.mindprod.inwords.Martian=Martian
//com.mindprod.inwords.Grams=Metric Prefixes (grams)
//com.mindprod.inwords.Norwegian=Norwegian
//com.mindprod.inwords.Octal=Octal
//com.mindprod.inwords.Polish=Polish (requires Unicode)
//com.mindprod.inwords.RAM=RAM (bytes)
//com.mindprod.inwords.Roman=Roman Numerals
//com.mindprod.inwords.Spanish=Spanish
//com.mindprod.inwords.SpanishForCheques=Spanish for cheques
//com.mindprod.inwords.Swedish=Swedish
//com.mindprod.inwords.SwissFrench=Swiss Vaudois French
//com.mindprod.inwords.Tagalog=Tagalog (Filipino)
//com.mindprod.inwords.TimeInterval=Time Interval in Milliseconds
//#-30-
String[][] result = null;
try
{
// look in jar, and on classpath for
// com.mindprod.inwords.inwords.properties
InputStream fis = InWords.class
.getResourceAsStream( "inwords.properties" );
if ( fis == null )
{
throw new IOException( "missing resource" );
}
result = Misc.loadProperties( fis );
}
catch ( IOException oops )
{
out.println( oops + " Problem accessing inwords.properties file." );
System.exit( 1 );
}
// in pairs className=languageNames
String[] languageClass = result[ 0 ];
languageNames = result[ 1 ];
languageDelegate = new ToWords[ languageClass.length ];
for ( int i = 0; i < languageClass.length; i++ )
{
try
{
languageDelegate[ i ] = ( ToWords ) ( Class
.forName( languageClass[ i ] ).newInstance() );
// could give the method a test, but that would pre-load the
// class
// languageDelegate[i].toWords(0);
}
catch ( Exception oops )
{
out.println( oops
+ " Bug in inwords.properties or class file for "
+ languageNames[ i ] );
System.exit( 1 );
}
} // end for
} // end findLanguages
/**
* layout components in a GridBag
*
* @param contentPane where to place the components.
*/
private void layoutComponents( Container contentPane )
{
// ---- 0 -----1--------
// 0 title --- about---- 0
// 1 title2 ---about---- 1
// 2 language number---- 2
// 3 lang-lab num-lab -- 3
// 4 ---- display words- 4
// 5 -----instructions-- 5
// ----0 ------- 1------
// x y w h wtx wty anchor fill T L B R padx pady
contentPane.add( title,
new GridBagConstraints( 0,
0,
1,
1,
0.0,
0.0,
GridBagConstraints.WEST,
GridBagConstraints.NONE,
new Insets( 10, 10, 5, 5 ),
0,
0 )
);
// x y w h wtx wty anchor fill T L B R padx pady
contentPane.add( title2,
new GridBagConstraints( 0,
1,
1,
1,
0.0,
0.0,
GridBagConstraints.WEST,
GridBagConstraints.NONE,
new Insets( 0, 10, 5, 5 ),
0,
0 )
);
// x y w h wtx wty anchor fill T L B R padx pady
contentPane.add( about,
new GridBagConstraints( 1,
0,
1,
2,
0.0,
0.0,
GridBagConstraints.EAST,
GridBagConstraints.NONE,
new Insets( 10, 10, 5, 10 ),
10,
2 )
);
// x y w h wtx wty anchor fill T L B R padx pady
contentPane.add( language,
new GridBagConstraints( 0,
2,
1,
1,
0.0,
0.0,
GridBagConstraints.WEST,
GridBagConstraints.HORIZONTAL,
new Insets( 0, 10, 5, 10 ),
0,
0 )
);
// x y w h wtx wty anchor fill T L B R padx pady
contentPane.add( spinner,
new GridBagConstraints( 1,
2,
1,
1,
0.0,
0.0,
GridBagConstraints.EAST,
GridBagConstraints.HORIZONTAL,
new Insets( 0, 10, 5, 10 ),
40,
0 )
);
// x y w h wtx wty anchor fill T L B R padx pady
contentPane.add( languageLabel,
new GridBagConstraints( 0,
3,
1,
1,
0.0,
0.0,
GridBagConstraints.CENTER,
GridBagConstraints.NONE,
new Insets( 0, 10, 5, 10 ),
0,
0 )
);
// x y w h wtx wty anchor fill T L B R padx pady
contentPane.add( theNumberLabel,
new GridBagConstraints( 1,
3,
1,
1,
0.0,
0.0,
GridBagConstraints.CENTER,
GridBagConstraints.NONE,
new Insets( 0, 10, 5, 10 ),
0,
0 )
);
// x y w h wtx wty anchor fill T L B R padx pady
contentPane.add( display,
new GridBagConstraints( 0,
4,
2,
1,
0.0,
1.0,
GridBagConstraints.CENTER,
GridBagConstraints.BOTH,
new Insets( 0, 10, 5, 10 ),
0,
0 )
);
// x y w h wtx wty anchor fill T L B R padx pady
contentPane.add( instructions,
new GridBagConstraints( 0,
5,
2,
1,
0.0,
0.0,
GridBagConstraints.CENTER,
GridBagConstraints.NONE,
new Insets( 0, 10, 10, 10 ),
0,
0 )
);
}
/**
* Refresh screen after language or number change.
*/
private void refresh()
{
final long setting = spinnerNumberModel.getNumber().longValue();
display.setText( languageDelegate[ language.getSelectedIndex() ].toWords( setting ) );
} // end refresh
/**
* Allow this Applet to run as as application as well.
*
* @param args command line arguments ignored.
*/
public static void main( String args[] )
{
HybridJ.fireup( new InWords(),
TITLE_STRING + " " + VERSION_STRING,
APPLET_WIDTH,
APPLET_HEIGHT );
} // end main
/**
* Called by the browser or Applet viewer to inform
* this Applet that it is being reclaimed and that it should destroy
* any resources that it has allocated.
*/
public void destroy()
{
about = null;
display = null;
instructions = null;
language = null;
languageLabel = null;
spinner = null;
spinnerNumberModel = null;
theNumberLabel = null;
title2 = null;
title = null;
}
/**
* Called by the browser or Applet viewer to inform
* this Applet that it has been loaded into the system.
*/
@Override
public void init()
{
if ( !VersionCheck.isJavaVersionOK( 1, 8, 0, this ) )
{
return;
}
Common18.setLaf();
final Container contentPane = this.getContentPane();
contentPane.setBackground( BACKGROUND_FOR_BODY );
contentPane.setLayout( new GridBagLayout() );
about = new JEButton( "About" );
about.setToolTipText( "About " + TITLE_STRING + " " + VERSION_STRING );
about.addActionListener( new ActionListener()
{
public void actionPerformed( ActionEvent e )
{
// open aboutbox frame
new CMPAboutJBox( TITLE_STRING,
VERSION_STRING,
"Spells out numbers in words in many languages.",
"Teaches you to count in many languages.",
"freeware",
RELEASE_DATE,
FIRST_COPYRIGHT_YEAR,
"Roedy Green",
"INWORDS",
"1.7" /* jdk version */ );
}
} );
title = new JLabel( TITLE_STRING + " " + VERSION_STRING );
title.setFont( FONT_FOR_TITLE );
title.setForeground( FOREGROUND_FOR_TITLE );
title2 = new JLabel(
"released:" +
RELEASE_DATE +
" build:" +
Build.BUILD_NUMBER
);
title2.setFont( FONT_FOR_TITLE2 );
title2.setForeground( FOREGROUND_FOR_TITLE );
// add legal choices to boxes
findLanguages();
language = new JComboBox<>( languageNames );
// turn off the write-in feature
language.setEditable( false );
language.setFont( FontFactory.build( "Dialog", Font.BOLD, 12 ) );
language.setSelectedItem( "English (North American)" );
language.addItemListener( new ItemListener()
{
/**
* Notice any change to language choice
* @param event details of just what the user clicked.
*/
public void itemStateChanged( ItemEvent event )
{
if ( event.getSource() == language )
{
refresh();
} // end if
} // end itemStateChanged
} );
languageLabel = new JLabel( "language" );
languageLabel.setFont( FontFactory.build( "Dialog", Font.BOLD, 15 ) );
languageLabel.setForeground( FOREGROUND_FOR_LABEL );
/* JSpinner with commas */
spinner = new JSpinner();
spinner.setFont( FontFactory.build( "Dialog", Font.BOLD, 15 ) );
spinner.setEnabled( true );
// how much space you want to freeze the spinner at.
Dimension d = new Dimension( 200, 30 );
spinner.setMinimumSize( d );
spinner.setPreferredSize( d );
spinner.setMaximumSize( d );
// NumberModel tracks value, but not how it is formatted.
spinnerNumberModel =
new SpinnerNumberModel( /* initial value */ 12345L,
/* min */ Long.MIN_VALUE + 1, /* -9,223,372,036,854,775,807 */
/* max */ Long.MAX_VALUE, /* 9,223,372,036,854,775,807 */
/* step */ 1 );
spinner.setModel( spinnerNumberModel );
spinner.addChangeListener( new ChangeListener()
{
/**
* Invoked when the JSpinner changes, even one notch in
* moving to another value
* @param e a ChangeEvent object
*/
public void stateChanged( ChangeEvent e )
{
// do something when user changes the value.
refresh();
}
} );
theNumberLabel = new JLabel( "number" );
theNumberLabel.setFont( FontFactory.build( "Dialog", Font.BOLD, 15 ) );
theNumberLabel.setForeground( FOREGROUND_FOR_LABEL );
display = new JTextArea();
display.setLineWrap( true );
display.setWrapStyleWord( true );
display.setEditable( false );
// Unicode is a CMP virtual font mapped onto a font that supports a wide set of chars
display.setFont( FontFactory.build( "Unicode", Font.PLAIN, 15 ) );
display.setForeground( FOREGROUND_FOR_DISPLAY );
instructions = new JLabel( "Select language, "
+ "enter number (with or without commas), "
+ "and hit enter." );
instructions.setForeground( FOREGROUND_FOR_INSTRUCTIONS );
refresh();
layoutComponents( contentPane );
this.validate();
this.setVisible( true );
} // end init
}