net.nand.util.i18n.gui
Class PropertiesTranslatorEditor

java.lang.Object
  extended by net.nand.util.i18n.gui.PropertiesTranslatorEditor
All Implemented Interfaces:
java.awt.event.ActionListener, java.awt.event.MouseListener, java.util.EventListener

public class PropertiesTranslatorEditor
extends java.lang.Object
implements java.awt.event.ActionListener, java.awt.event.MouseListener

Property file editor for translators (side-by-side source and destination languages). Presents the source and destination language keys and values. Highlights values that still need to be translated into the destination. Saves in the ISO-8859-1 encoding required for .properties files, escaping unicode characters where needed.

The main startup class for this package is PTEMain, which has buttons for New, Open, About, etc.

Work in progress. Current limitations:

There are other properties editors out there, I wanted to see what writing one would be like.

Author:
Jeremy D Monin <jeremy@nand.net>

Nested Class Summary
private  class PropertiesTranslatorEditor.CellEditorMouseListener
          Listener to double-click while editing cell text, to bring up a larger edit dialog.
static class PropertiesTranslatorEditor.CellStatus
          Return types for PropertiesTranslatorEditor.PTSwingTableModel.getCellStatus(int, int)
private static class PropertiesTranslatorEditor.PTCellRenderer
          Rendering model (background colors, etc) for jtab.
private  class PropertiesTranslatorEditor.PTSwingTableModel
          Data model for viewing/editing data in jtab.
 class PropertiesTranslatorEditor.SearchPanel
          JPanel for the text search bar.
 
Field Summary
private static java.util.regex.Pattern _regex_findBaseAndLang
          Initialized and used in findBaseAndLangInFilename(String)
private  javax.swing.JButton bFind
          Find button, shows search panel sPan at bottom of window
private  javax.swing.JButton bHelp
          Help button, brings up a brief text message dialog
private  javax.swing.JButton bSaveDest
          Save button for properties file from current editor contents; disabled until changes are made
private  javax.swing.JButton bSaveSrc
          Save button for properties file from current editor contents; disabled until changes are made
private  PropertiesTranslatorEditor.CellEditorMouseListener cellListener
          Listener to double-click while editing cell text, to bring up a larger edit dialog.
private  boolean isDestNew
          If true, setDestIsNew(List) has been called
private  javax.swing.JFrame jfra
          main window, set up in init()
private  javax.swing.JScrollPane jpane
          main window's pane with jtab, created in init(), populated in #showPairInPane()
private  javax.swing.JTable jtab
          mainwindow's data table, shown within jpane, created and populated in #showPairInPane()
private  int jtabClickedCol
          Last-clicked model column number in jtab, or -1.
private  int jtabClickedRow
          Last-clicked row number in jtab, or -1
private  javax.swing.JMenuItem menuAddAbove
          Menu items to add a line above or below this line
private  javax.swing.JMenuItem menuAddBelow
          Menu items to add a line above or below this line
private  javax.swing.JMenuItem menuCopyToClip
          Menu item to copy cell contents to clipboard, or null if not allowed.
private  PropertiesTranslatorEditor.PTSwingTableModel mod
          data model for JTable
private  javax.swing.JPopupMenu mTablePopup
          Context popup menu within table.
private  java.util.List<java.lang.String> newDestComments
          If isDestNew, optional set of header comments, or null.
private  ParsedPropsFilePair pair
          Pair of properties files being edited, and their contents.
private  PropertiesTranslatorEditor.SearchPanel sPan
          Text search panel, not always visible, at bottom of window below jtab
(package private) static StringManager strings
          i18n text strings; if null, call initStringManager() to initialize.
 
Constructor Summary
PropertiesTranslatorEditor(java.io.File dest)
          Editor where the source filename will be derived from the destination filename.
PropertiesTranslatorEditor(java.io.File src, java.io.File dest)
          Editor with source and destination files specified.
 
Method Summary
 void actionPerformed(java.awt.event.ActionEvent ae)
          Handle button clicks.
 boolean checkUnsavedBeforeDispose()
          Checks for unsaved changes (pair.unsavedSrc || pair.unsavedDest) and if any, ask the user if they want to save before closing.
 void doMouseEvent(java.awt.event.MouseEvent e, boolean isPress)
          Handle mouse clicks in table cells, including context-menu popup trigger; will set currently selected cell at mousePressed before showing popup.
 void doSearchHotkey()
          Show and focus the Search bar when its hotkey (Ctrl-F 'Find') is pressed.
static java.lang.String[] findBaseAndLangInFilename(java.lang.String propsFilename)
          Given a properties file name, try to determine its base name and language if any.
 boolean hasUnsavedChanges()
          Are there any unsaved changes in the destination and/or source properties files?
 void init()
          Continue GUI startup, once constructor has set pair or left it null.
(package private) static void initStringManager()
          Initialize strings with the properties bundle at net/nand/util/i18n/gui/strings/pte.properties in the default locale.
private  void insertRow(java.awt.event.ActionEvent ae, boolean beforeRow)
          Add/insert a row before or after the row that was right-clicked on, which was stored at click time in jtabClickedRow.
static void main(java.lang.String[] args)
          'Local' main program; the package's actual main is PTEMain.
static java.io.File makeParentFilename(java.lang.String destFilename)
          Given a more-specific destination locale filename, calculate the less-specific source filename by removing _xx suffix(es) and check whether that source exists.
 void mouseClicked(java.awt.event.MouseEvent e)
           
 void mouseEntered(java.awt.event.MouseEvent e)
           
 void mouseExited(java.awt.event.MouseEvent e)
           
 void mousePressed(java.awt.event.MouseEvent e)
           
 void mouseReleased(java.awt.event.MouseEvent e)
           
private static void runUnitTests()
          A few quick unit tests
 void saveChangesToAny()
          Save any unsaved changes to the destination and/or source properties files.
 void saveChangesToDest()
          Save changes to the destination file, and clear the pair.unsavedDest flag.
 void saveChangesToSrc()
          Save changes to the source file, and clear the pair.unsavedSrc flag.
 void setDestIsNew(java.util.List<java.lang.String> comments)
          Indicate that the destination is new (not yet existing): init() will then ignore FileNotFoundException for destination.
private  void updateSaveButtonsForEditing(boolean startEditing, boolean isSrc)
          Enable/disable the Save Src or Save Dest button while editing a cell.
 
Methods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
 

Field Detail

strings

static StringManager strings
i18n text strings; if null, call initStringManager() to initialize.


pair

private ParsedPropsFilePair pair
Pair of properties files being edited, and their contents. The files are pair.srcFile and pair.destFile.

null if we start with no parameters, and wait for a file-open dialog from PTEMain.


isDestNew

private boolean isDestNew
If true, setDestIsNew(List) has been called


newDestComments

private java.util.List<java.lang.String> newDestComments
If isDestNew, optional set of header comments, or null. Same format as PropsFileParser.KeyPairLine#comment. Set to null during init() to free up the reference for gc.


jfra

private javax.swing.JFrame jfra
main window, set up in init()


jpane

private javax.swing.JScrollPane jpane
main window's pane with jtab, created in init(), populated in #showPairInPane()


bFind

private javax.swing.JButton bFind
Find button, shows search panel sPan at bottom of window


bHelp

private javax.swing.JButton bHelp
Help button, brings up a brief text message dialog


bSaveSrc

private javax.swing.JButton bSaveSrc
Save button for properties file from current editor contents; disabled until changes are made


bSaveDest

private javax.swing.JButton bSaveDest
Save button for properties file from current editor contents; disabled until changes are made


mTablePopup

private javax.swing.JPopupMenu mTablePopup
Context popup menu within table. Null before init().


menuAddAbove

private javax.swing.JMenuItem menuAddAbove
Menu items to add a line above or below this line


menuAddBelow

private javax.swing.JMenuItem menuAddBelow
Menu items to add a line above or below this line


menuCopyToClip

private javax.swing.JMenuItem menuCopyToClip
Menu item to copy cell contents to clipboard, or null if not allowed. init() calls SecurityManager.checkSystemClipboardAccess() to check if allowed.


jtab

private javax.swing.JTable jtab
mainwindow's data table, shown within jpane, created and populated in #showPairInPane()


sPan

private PropertiesTranslatorEditor.SearchPanel sPan
Text search panel, not always visible, at bottom of window below jtab


mod

private PropertiesTranslatorEditor.PTSwingTableModel mod
data model for JTable


jtabClickedRow

private int jtabClickedRow
Last-clicked row number in jtab, or -1


jtabClickedCol

private int jtabClickedCol
Last-clicked model column number in jtab, or -1. Columns stretch to fit the table width, so a click will always be in a column.


cellListener

private PropertiesTranslatorEditor.CellEditorMouseListener cellListener
Listener to double-click while editing cell text, to bring up a larger edit dialog. Added/removed to edit component in JTable.prepareEditor(TableCellEditor, int, int) / JTable.removeEditor().


_regex_findBaseAndLang

private static java.util.regex.Pattern _regex_findBaseAndLang
Initialized and used in findBaseAndLangInFilename(String)

Constructor Detail

PropertiesTranslatorEditor

public PropertiesTranslatorEditor(java.io.File src,
                                  java.io.File dest)
Editor with source and destination files specified. Call init() to bring up the GUI and parse the properties files.

Parameters:
src - Source language/locale properties file
dest - Destination language/locale properties file

PropertiesTranslatorEditor

public PropertiesTranslatorEditor(java.io.File dest)
                           throws java.lang.IllegalArgumentException,
                                  java.io.FileNotFoundException
Editor where the source filename will be derived from the destination filename. Call init() to bring up the GUI and parse the properties files.

Parameters:
dest - Destination language properties file, full or relative path. Its filename must end with "_xx.properties" and the source will be the same filename without the "_xx" part. (The "_xx" part can be any length, not limited to 2 letters.) This constructor will call makeParentFilename({@link File#getPath() dest.getPath()).
Throws:
java.lang.IllegalArgumentException - Unless destFilename ends with _xx.properties (xx = any code 2 or more chars long)
java.io.FileNotFoundException - if no existing parent of dest can be found on disk by makeParentFilename(String)
Method Detail

setDestIsNew

public void setDestIsNew(java.util.List<java.lang.String> comments)
Indicate that the destination is new (not yet existing): init() will then ignore FileNotFoundException for destination. Optionally provide header comments to place in the destination.

Parameters:
comments - any comment lines to use as the initial contents; same format as PropsFileParser.KeyPairLine#comment. Otherwise null.

init

public void init()
Continue GUI startup, once constructor has set pair or left it null. Will start the GUI and then parse pair's files from its srcFile and destFile fields. If the destination is new (not yet existing), call setDestIsNew(List) before this method.


actionPerformed

public void actionPerformed(java.awt.event.ActionEvent ae)
Handle button clicks.

Specified by:
actionPerformed in interface java.awt.event.ActionListener

doSearchHotkey

public void doSearchHotkey()
Show and focus the Search bar when its hotkey (Ctrl-F 'Find') is pressed. If the cell being edited would be covered, scroll up.


updateSaveButtonsForEditing

private void updateSaveButtonsForEditing(boolean startEditing,
                                         boolean isSrc)
Enable/disable the Save Src or Save Dest button while editing a cell. Save is always enabled while editing; after editing, it's disabled if its file's unsaved flag is false. If the user changes a cell, that will separately fire PropertiesTranslatorEditor.PTSwingTableModel.setValueAt(Object, int, int) which will set the file's unsaved flag and enable the button again.

Parameters:
startEditing - True if the user just started editing the cell, false if they just finished editing
isSrc - True for source column, false for destination. Ignored unless startEditing because the JTable method called after editing doesn't give the column, so we check both buttons.

insertRow

private void insertRow(java.awt.event.ActionEvent ae,
                       boolean beforeRow)
Add/insert a row before or after the row that was right-clicked on, which was stored at click time in jtabClickedRow.

Parameters:
ae - Menu popup action, with right-click location
beforeRow - If true, insert before (above), otherwise add after (below) this line

hasUnsavedChanges

public boolean hasUnsavedChanges()
Are there any unsaved changes in the destination and/or source properties files?

See Also:
checkUnsavedBeforeDispose(), saveChangesToAny()

saveChangesToAny

public void saveChangesToAny()
Save any unsaved changes to the destination and/or source properties files.


saveChangesToDest

public void saveChangesToDest()
Save changes to the destination file, and clear the pair.unsavedDest flag. If pair.unsavedInsRows is set, calls pair.convertInsertedRows() before saving. Will save even if the pair.unsavedDest flag is false.


saveChangesToSrc

public void saveChangesToSrc()
Save changes to the source file, and clear the pair.unsavedSrc flag. If pair.unsavedInsRows is set, calls pair.convertInsertedRows() before saving. Will save even if the pair.unsavedSrc flag is false.


checkUnsavedBeforeDispose

public boolean checkUnsavedBeforeDispose()
Checks for unsaved changes (pair.unsavedSrc || pair.unsavedDest) and if any, ask the user if they want to save before closing. Calls saveChangesToAny() if user clicks yes.

Returns:
True if okay to dispose of this editor frame: Not any unsaved changes, or user clicked Yes to save them, or user clicked No (don't save changes). False if unsaved changes and user clicked Cancel (don't close).
See Also:
hasUnsavedChanges()

makeParentFilename

public static java.io.File makeParentFilename(java.lang.String destFilename)
                                       throws java.lang.IllegalArgumentException,
                                              java.lang.NullPointerException
Given a more-specific destination locale filename, calculate the less-specific source filename by removing _xx suffix(es) and check whether that source exists.

Parameters:
destFilename - Destination language properties file, full or relative path. This filename must end with "_xx.properties" and the source will be the same filename without the "_xx" part. (The "_xx" part can be any length, not limited to 2 letters.)
Returns:
Parent file for this destination, or null if none exists on disk
Throws:
java.lang.IllegalArgumentException - Unless destFilename ends with _xx.properties (xx = any code 2 or more chars long)
java.lang.NullPointerException - if destFilename is null

findBaseAndLangInFilename

public static java.lang.String[] findBaseAndLangInFilename(java.lang.String propsFilename)
                                                    throws java.lang.NullPointerException
Given a properties file name, try to determine its base name and language if any. The base name is the part preceding the optional _language and _COUNTRY/_REGION suffixes.

Assumes the base name won't contain 2 or 3 lowercase letters between underscores, which looks like a language suffix. This is more strict than the spec for java identifiers (thus classes/props file base names) which allows underscores.

Process:

Parameters:
propsFilename - Properties filename to examine; see above for assumptions and process
Returns:
A 2-element array containing the base name and 2- or 3-letter language; if no language is found, that array element will be null. If the base name can't be determined, returns null instead of an array.
Throws:
java.lang.NullPointerException - if propsFilename is null

doMouseEvent

public void doMouseEvent(java.awt.event.MouseEvent e,
                         boolean isPress)
Handle mouse clicks in table cells, including context-menu popup trigger; will set currently selected cell at mousePressed before showing popup.

Parameters:
isPress - True for mousePressed, to change selected cell

mousePressed

public void mousePressed(java.awt.event.MouseEvent e)
Specified by:
mousePressed in interface java.awt.event.MouseListener

mouseReleased

public void mouseReleased(java.awt.event.MouseEvent e)
Specified by:
mouseReleased in interface java.awt.event.MouseListener

mouseClicked

public void mouseClicked(java.awt.event.MouseEvent e)
Specified by:
mouseClicked in interface java.awt.event.MouseListener

mouseEntered

public void mouseEntered(java.awt.event.MouseEvent e)
Specified by:
mouseEntered in interface java.awt.event.MouseListener

mouseExited

public void mouseExited(java.awt.event.MouseEvent e)
Specified by:
mouseExited in interface java.awt.event.MouseListener

runUnitTests

private static void runUnitTests()
A few quick unit tests


initStringManager

static void initStringManager()
Initialize strings with the properties bundle at net/nand/util/i18n/gui/strings/pte.properties in the default locale.


main

public static void main(java.lang.String[] args)
                 throws java.lang.IllegalStateException,
                        java.io.IOException
'Local' main program; the package's actual main is PTEMain. This main will bring up the PTEMain main menu, or an editor for .properties filename(s) on the command line, or run a few unit tests.

Parameters:
args - Path/Filename of destination .properties, or source and destination. If destination only, filename must end with "_xx.properties" and the source will be the same filename without the "_xx" part. Or, can contain --test to run a few unit tests.
Throws:
java.io.IOException - if a properties file does not exist or cannot be read
java.lang.IllegalStateException - if any call-sequence errors occur