/*
* $Id$
*
* Copyright (c) 2000-2007 by Rodney Kinney, Brent Easton
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License (LGPL) as published by the Free Software Foundation.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, copies are available
* at http://www.opensource.org.
*/
package VASSAL.i18n;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusListener;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.IOException;
import java.io.Serializable;
import java.util.EventObject;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTable;
import javax.swing.JTree;
import javax.swing.ListSelectionModel;
import javax.swing.SwingUtilities;
import javax.swing.event.CellEditorListener;
import javax.swing.event.ChangeEvent;
import javax.swing.event.EventListenerList;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.TreeCellEditor;
import javax.swing.tree.TreeSelectionModel;
import VASSAL.build.Configurable;
import VASSAL.build.GameModule;
import VASSAL.build.module.documentation.HelpFile;
import VASSAL.build.module.documentation.HelpWindow;
import VASSAL.configure.ConfigureTree;
import VASSAL.configure.PropertiesWindow;
import VASSAL.configure.ShowHelpAction;
import VASSAL.tools.WriteErrorDialog;
/**
* Window for editing translations of a {@link Configurable} object
*/
public class TranslateWindow extends JDialog implements ListSelectionListener,
TreeSelectionListener {
private static final long serialVersionUID = 1L;
protected static Color TRANSLATION_NEEDED_COLOR = Color.red;
protected static Color TRANSLATION_DONE_COLOR = Color.blue;
protected static Color NO_TRANSLATION_NEEDED_COLOR = Color.black;
protected Translatable target;
protected String[] keys;
protected JTable keyTable;
protected Translatable keyTarget;
protected JTree tree;
protected Translation currentTranslation = null;
protected JComboBox langBox;
protected ActionListener boxListener;
protected int lastSelectedLangIndex;
protected String currentKey = ""; //$NON-NLS-1$
protected ConfigureTree myConfigureTree;
protected CopyButton[] copyButtons;
public TranslateWindow(Frame owner, boolean modal, final Translatable target,
HelpWindow helpWindow, ConfigureTree tree) {
super(owner, modal);
this.target = target;
myConfigureTree = tree;
initComponents();
}
protected void initComponents() {
setTitle("Translate " + VASSAL.configure.ConfigureTree.getConfigureName((Configurable) target));
JPanel mainPanel = new JPanel(new BorderLayout());
/*
* Place Language selector above Tree and Keys
*/
mainPanel.add(getHeaderPanel(), BorderLayout.PAGE_START);
mainPanel.add(buildMainPanel(), BorderLayout.CENTER);
mainPanel.add(getButtonPanel(), BorderLayout.PAGE_END);
add(mainPanel);
pack();
setLocationRelativeTo(getParent());
setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent we) {
cancel();
}
});
}
protected Component getHeaderPanel() {
JPanel langPanel = new JPanel();
langPanel.add(new JLabel("Language: "));
langBox = new JComboBox(Localization.getInstance().getTranslationList());
langPanel.add(langBox);
boxListener = new ActionListener() {
public void actionPerformed(ActionEvent e) {
commitTableEdit();
String selectedTranslation = (String) ((JComboBox) e.getSource()).getSelectedItem();
changeLanguage(selectedTranslation);
}
};
langBox.addActionListener(boxListener);
if (Localization.getInstance().getTranslationList().length > 0) {
langBox.setSelectedIndex(0);
}
langPanel.setMinimumSize(new Dimension(800, 0));
JButton addButton = new JButton("Add translation");
addButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
getNewTranslation();
}});
langPanel.add(addButton);
return langPanel;
}
/**
* User has clicked on the Add Translation button. Create a new
* PropertiesWindow for a translation and display it.
*
*/
protected void getNewTranslation() {
Translation t = new Translation();
PropertiesWindow w = new MyPropertiesWindow((Frame) SwingUtilities.getAncestorOfClass(Frame.class, this), false, t, null, this);
w.setVisible(true);
}
/**
* Called from MyPropertiesWindow when the user saves the new translation
* @param target new Translation
*/
protected void refreshTranslationList(Configurable target) {
Language language = GameModule.getGameModule().getComponentsOf(Language.class).iterator().next();
if (language != null) {
myConfigureTree.externalInsert(language, target);
}
langBox.removeAllItems();
String[] langs = Localization.getInstance().getTranslationList();
for (int i=0; i < langs.length; i++) {
langBox.addItem(langs[i]);
}
langBox.setSelectedItem(((Translation) target).getDescription());
keyTable.setEnabled(true);
tree.repaint();
}
protected static class MyPropertiesWindow extends PropertiesWindow {
private static final long serialVersionUID = 1L;
protected Configurable myTarget;
protected TranslateWindow owningWindow;
public MyPropertiesWindow(Frame owner, boolean modal, final Configurable target, HelpWindow helpWindow, TranslateWindow tw) {
super(owner, modal, target, helpWindow);
myTarget = target;
owningWindow = tw;
}
public void save() {
super.save();
owningWindow.refreshTranslationList(myTarget);
}
public void cancel() {
dispose();
}
}
protected Component buildMainPanel() {
JPanel keyPanel = buildKeyTablePanel();
/*
* Tree of all components from target component down
*/
JPanel treePanel = new JPanel(new BorderLayout());
MyTreeNode top = new MyTreeNode(target);
createNodes(top);
tree = new JTree(top);
tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
tree.addTreeSelectionListener(this);
tree.setSelectionRow(0);
tree.setCellRenderer(new MyTreeCellRenderer());
JScrollPane treeScroll = new JScrollPane(tree, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
treePanel.add(treeScroll, BorderLayout.CENTER);
treePanel.setMinimumSize(new Dimension(400, 100));
treePanel.setPreferredSize(new Dimension(800, 300));
/*
* First split between Tree display and Keys
*/
JSplitPane split1 = new JSplitPane(JSplitPane.VERTICAL_SPLIT, treePanel, keyPanel);
split1.setResizeWeight(0.5);
return split1;
}
protected JPanel buildKeyTablePanel() {
/*
* Key Panel - Table of Keys for the component currently selected in the
* Tree Panel
*/
JPanel keyPanel = new JPanel(new BorderLayout());
keyPanel.setMinimumSize(new Dimension(800, 100));
keyTable = new MyTable();
keyTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
keyTable.addFocusListener(new FocusListener() {
public void focusGained(java.awt.event.FocusEvent e) {
}
public void focusLost(java.awt.event.FocusEvent e) {
commitTableEdit();
}
});
keyTable.getSelectionModel().addListSelectionListener(this);
keyTable.setEnabled(currentTranslation != null);
JScrollPane keyScroll = new JScrollPane(keyTable, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
keyPanel.add(keyScroll, BorderLayout.CENTER);
keyPanel.setMinimumSize(new Dimension(400, 100));
keyPanel.setPreferredSize(new Dimension(800, 200));
return keyPanel;
}
protected Component getButtonPanel() {
final JPanel buttonBox = new JPanel();
final JButton helpButton = new JButton(Resources.getString(Resources.HELP));
helpButton.addActionListener(new ShowHelpAction(HelpFile.getReferenceManualPage("Translations.htm","module").getContents(),null));
buttonBox.add(helpButton);
final JButton okButton = new JButton(Resources.getString(Resources.OK));
okButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
try {
save();
}
catch (IOException e1) {
WriteErrorDialog.error(e1,
GameModule.getGameModule().getArchiveWriter().getName());
}
}
});
buttonBox.add(okButton);
final JButton cancelButton = new JButton(
Resources.getString(Resources.CANCEL));
cancelButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
cancel();
}
});
buttonBox.add(cancelButton);
return buttonBox;
}
// Workaround for JRE Bug 4709394 - Cell editing lost when JTable loses
// focus. Call this all over the place!
protected void commitTableEdit() {
if (keyTable != null && keyTable.isEditing()) {
int row = keyTable.getEditingRow();
int column = keyTable.getEditingColumn();
if (row != -1 && column != -1)
keyTable.editCellAt(row, column);
}
}
/**
* New Language selected from the drop-down box
*/
protected void changeLanguage(String selectedTranslation) {
if (currentTranslation != null) {
if (currentTranslation.isDirty()) {
try {
if (!querySave()) {
langBox.removeActionListener(boxListener);
langBox.setSelectedItem(lastSelectedLangIndex);
langBox.addActionListener(boxListener);
}
}
catch (IOException e) {
WriteErrorDialog.error(e,
GameModule.getGameModule().getArchiveWriter().getName());
}
}
}
currentTranslation = Localization.getInstance().getTranslation(selectedTranslation);
lastSelectedLangIndex = langBox.getSelectedIndex();
if (keyTable != null) {
((MyTableModel) keyTable.getModel()).update();
}
}
/**
* When a new node is selected, display keys for the new component in the keys
* table
*/
public void valueChanged(TreeSelectionEvent e) {
commitTableEdit();
MyTreeNode node = (MyTreeNode) tree.getLastSelectedPathComponent();
if (node == null)
return;
keys = node.getTarget().getI18nData().getAttributeKeys().toArray(new String[0]);
copyButtons = new CopyButton[keys.length];
keyTarget = node.getTarget();
((AbstractTableModel) keyTable.getModel()).fireTableStructureChanged();
if (keys != null && keys.length > 0) {
keyTable.getSelectionModel().setSelectionInterval(0, 0);
}
((MyTableModel) keyTable.getModel()).update();
}
/**
* When a key is selected in the table, display the source and translated
* texts in the right hand panels
*/
public void valueChanged(ListSelectionEvent e) {
if (e.getValueIsAdjusting())
return;
ListSelectionModel lsm = (ListSelectionModel) e.getSource();
if (lsm.isSelectionEmpty()) {
return;
}
else {
String key = keys[lsm.getMinSelectionIndex()];
currentKey = keyTarget.getI18nData().getFullPrefix() + key; //$NON-NLS-1$
}
}
/**
* Create the nodes for the JTree display
*/
protected void createNodes(MyTreeNode top) {
for (Translatable child : top.getTarget().getI18nData().getChildren()) {
MyTreeNode childNode = new MyTreeNode(child);
createNodes(childNode);
top.add(childNode);
}
}
public static String getDisplayName(Translatable t) {
if (t == null) {
return ""; //$NON-NLS-1$
}
String type = ConfigureTree.getConfigureName(t.getClass());
String name = ""; //$NON-NLS-1$
if (t instanceof Configurable) {
name = ((Configurable) t).getConfigureName();
}
String s = (name == null) ? "" : (name + " "); //$NON-NLS-1$ //$NON-NLS-2$
return s + " [" + type + "]"; //$NON-NLS-1$ //$NON-NLS-2$
}
/**
* Cancel button clicked. Check for outstanding changes.
*/
protected void cancel() {
commitTableEdit();
if (currentTranslation != null) {
if (currentTranslation.isDirty()) {
try {
if (!querySave()) {
return;
}
}
catch (IOException e) {
WriteErrorDialog.error(e,
GameModule.getGameModule().getArchiveWriter().getName());
}
}
}
dispose();
}
protected boolean querySave() throws IOException {
switch (JOptionPane.showConfirmDialog(this, "Do you want to save these changes now?",
"Unsaved Changes", JOptionPane.YES_NO_CANCEL_OPTION)) {
case JOptionPane.YES_OPTION:
saveTranslation();
return true;
case JOptionPane.NO_OPTION:
reloadTranslation();
return true;
case JOptionPane.CANCEL_OPTION:
return false;
}
return true;
}
/**
* Save button clicked
* @throws IOException
*/
protected void save() throws IOException {
commitTableEdit();
if (saveTranslation()) {
dispose();
}
}
/**
* Save the current Translation
* @throws IOException
*/
protected boolean saveTranslation() throws IOException {
if (currentTranslation != null) {
currentTranslation.saveProperties();
}
return true;
}
/**
* Reload the current translation from the archive
* @throws IOException
*/
protected void reloadTranslation() throws IOException {
if (currentTranslation != null) {
currentTranslation.reloadProperties();
}
}
/**
* Custome JTable to support CopyButtons in JTable cells
*
*/
class MyTable extends JTable {
private static final long serialVersionUID = 1L;
public MyTable() {
super(new MyTableModel());
setDefaultRenderer( JComponent.class, new JComponentCellRenderer() );
setDefaultEditor( JComponent.class, new JComponentCellEditor() );
}
public TableCellRenderer getCellRenderer(int row, int column) {
TableColumn tableColumn = getColumnModel().getColumn(column);
TableCellRenderer renderer = tableColumn.getCellRenderer();
if (renderer == null) {
Class<?> c = getColumnClass(column);
if( c.equals(Object.class) )
{
Object o = getValueAt(row,column);
if( o != null )
c = getValueAt(row,column).getClass();
}
renderer = getDefaultRenderer(c);
}
return renderer;
}
public TableCellEditor getCellEditor(int row, int column) {
TableColumn tableColumn = getColumnModel().getColumn(column);
TableCellEditor editor = tableColumn.getCellEditor();
if (editor == null) {
Class<?> c = getColumnClass(column);
if( c.equals(Object.class) )
{
Object o = getValueAt(row,column);
if( o != null )
c = getValueAt(row,column).getClass();
}
editor = getDefaultEditor(c);
}
return editor;
}
}
/**
* Custome Cell Renderer to support CopyButtons in JTable cells
*
*/
protected static class JComponentCellRenderer implements TableCellRenderer
{
public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus, int row, int column) {
return (JComponent)value;
}
}
/**
* Custom CellEditor to support CopyButtons in JTable cells
*
*/
protected static class JComponentCellEditor
implements TableCellEditor, TreeCellEditor, Serializable {
private static final long serialVersionUID = 1L;
protected EventListenerList listenerList = new EventListenerList();
transient protected ChangeEvent changeEvent = null;
protected JComponent editorComponent = null;
protected JComponent container = null; // Can be tree or table
public Component getComponent() {
return editorComponent;
}
public Object getCellEditorValue() {
return editorComponent;
}
public boolean isCellEditable(EventObject anEvent) {
return true;
}
public boolean shouldSelectCell(EventObject anEvent) {
if( editorComponent != null && anEvent instanceof MouseEvent
&& ((MouseEvent)anEvent).getID() == MouseEvent.MOUSE_PRESSED )
{
Component dispatchComponent = SwingUtilities.getDeepestComponentAt(editorComponent, 3, 3 );
((CopyButton) dispatchComponent).setSelected(true);
}
return false;
}
public boolean stopCellEditing() {
return true;
}
public void cancelCellEditing() {
}
public void addCellEditorListener(CellEditorListener l) {
}
public void removeCellEditorListener(CellEditorListener l) {
}
// implements javax.swing.tree.TreeCellEditor
public Component getTreeCellEditorComponent(JTree tree, Object value,
boolean isSelected, boolean expanded, boolean leaf, int row) {
// String stringValue = tree.convertValueToText(value, isSelected,
// expanded, leaf, row, false);
editorComponent = (JComponent)value;
container = tree;
return editorComponent;
}
// implements javax.swing.table.TableCellEditor
public Component getTableCellEditorComponent(JTable table, Object value,
boolean isSelected, int row, int column) {
editorComponent = (JComponent)value;
container = table;
return editorComponent;
}
} // End of class JComponentCellEditor
/**
* Custom Key Table Model
*/
static final int ATTR_COL = 0;
static final int SOURCE_COL = 1;
static final int CC_COL = 2;
static final int TRAN_COL = 3;
class MyTableModel extends AbstractTableModel {
private static final long serialVersionUID = 1L;
public int getColumnCount() {
return 4;
}
public String getColumnName(int col) {
switch (col) {
case ATTR_COL:
return "Attribute";
case SOURCE_COL:
return "Source Text";
case CC_COL:
return "cc";
case TRAN_COL:
return "Translation";
}
return null;
}
public int getRowCount() {
return keys == null ? 0 : keys.length;
}
public Object getValueAt(int row, int col) {
switch (col) {
case ATTR_COL:
return keys == null ? null : keyTarget.getI18nData().getAttributeDescription(keys[row]);
case SOURCE_COL:
return keys == null ? null : keyTarget.getAttributeValueString(keys[row]);
case CC_COL:
if (copyButtons[row] == null) {
copyButtons[row] = new CopyButton(row);
}
return copyButtons[row];
case TRAN_COL:
if (currentTranslation != null) {
String key = keyTarget.getI18nData().getFullPrefix() + keys[row]; //$NON-NLS-1$
return currentTranslation.translate(key);
}
}
return null;
}
public void setValueAt(Object value, int row, int col) {
if (col == TRAN_COL) {
currentTranslation.setProperty(currentKey, (String) value);
copyButtons[row].checkEnabled();
fireTableCellUpdated(row, col);
tree.repaint();
}
}
public boolean isCellEditable(int row, int col) {
return col == TRAN_COL || col == CC_COL;
}
public void update() {
this.fireTableStructureChanged();
keyTable.getColumnModel().getColumn(ATTR_COL).setCellRenderer(new MyTableCellRenderer(keyTarget));
keyTable.getColumnModel().getColumn(CC_COL).setMaxWidth(25);
}
}
/**
* Custom table cell renderer - Change color of key names based on translation
* status
*/
class MyTableCellRenderer extends DefaultTableCellRenderer {
private static final long serialVersionUID = 1L;
protected Translatable target;
public MyTableCellRenderer(Translatable target) {
this.target = target;
}
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
boolean hasFocus, int row, int col) {
Component c = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row,
col);
String fullKey = target.getI18nData().getFullPrefix() + keys[row]; //$NON-NLS-1$
String translation = currentTranslation == null ? "" : currentTranslation.translate(fullKey);
String originalValue = target.getAttributeValueString(keys[row]);
if (originalValue == null || originalValue.length() == 0) {
c.setForeground(NO_TRANSLATION_NEEDED_COLOR);
}
else {
if (translation == null || translation.length() == 0) {
c.setForeground(TRANSLATION_NEEDED_COLOR);
}
else {
c.setForeground(TRANSLATION_DONE_COLOR);
}
}
return c;
}
}
/**
* Custom button to copy source to translation
*
*/
class CopyButton extends JButton implements ActionListener {
private static final long serialVersionUID = 1L;
int row;
public CopyButton(int i) {
super("->");
row = i;
addActionListener(this);
setMargin(new Insets(1, 1, 1, 1));
checkEnabled();
}
public void actionPerformed(ActionEvent e) {
String key = keys[row];
currentKey = keyTarget.getI18nData().getFullPrefix() + key; //$NON-NLS-1$
currentTranslation.setProperty(currentKey, keyTarget.getAttributeValueString(keys[row]));
checkEnabled();
((MyTableModel) keyTable.getModel()).update();
}
public void checkEnabled() {
if (keyTarget != null && keys != null && keys[row] != null) {
String t = currentTranslation == null ? "" : currentTranslation.translate(keyTarget.getI18nData().getFullPrefix() + keys[row]); //$NON-NLS-1$
setEnabled(t == null || t.length() == 0);
}
else {
setEnabled(true);
}
}
}
/**
* Custom tree cell renderer - Change color of component names based on
* translation status of children
*/
class MyTreeCellRenderer extends DefaultTreeCellRenderer {
private static final long serialVersionUID = 1L;
public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel,
boolean expanded, boolean leaf, int row, boolean hasFocus) {
Component c = super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row,
hasFocus);
Translatable t = ((MyTreeNode) value).getTarget();
if (t.getI18nData().hasUntranslatedAttributes(currentTranslation)) {
c.setForeground(TRANSLATION_NEEDED_COLOR);
}
else {
c.setForeground(NO_TRANSLATION_NEEDED_COLOR);
}
return c;
}
}
/**
* Custom Tree Node implementation
*/
protected static class MyTreeNode extends DefaultMutableTreeNode {
private static final long serialVersionUID = 1L;
Translatable component;
public MyTreeNode(Translatable t) {
component = t;
}
public Translatable getTarget() {
return component;
}
public String toString() {
return getDisplayName(component);
}
}
}