/** * */ package cz.cuni.mff.peckam.java.origamist.gui.common; import java.awt.Color; import java.awt.event.ActionEvent; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.LinkedHashSet; import java.util.List; import java.util.Locale; import javax.swing.AbstractAction; import javax.swing.BorderFactory; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import javax.swing.event.PopupMenuEvent; import javax.swing.event.PopupMenuListener; import javax.swing.text.JTextComponent; import com.jgoodies.forms.layout.CellConstraints; import com.jgoodies.forms.layout.FormLayout; import cz.cuni.mff.peckam.java.origamist.common.LangString; import cz.cuni.mff.peckam.java.origamist.common.jaxb.ObjectFactory; import cz.cuni.mff.peckam.java.origamist.configuration.Configuration; import cz.cuni.mff.peckam.java.origamist.services.ServiceLocator; import cz.cuni.mff.peckam.java.origamist.services.interfaces.ConfigurationManager; import cz.cuni.mff.peckam.java.origamist.utils.UniversalDocumentListener; /** * A text field allowing the user to input translations of a string in various locales. * * @param T The type of the text field to be used. * * @author Martin Pecka */ public class JLangStringListTextField<T extends JTextComponent> extends JPanel { /** */ private static final long serialVersionUID = -3484876190164726267L; /** The list of translatable strings. */ protected List<LangString> strings; /** The list of available locales. */ protected JLocaleComboBox locales; /** The textual value of the currently chosen locale. */ protected T value; /** If not <code>null</code>, the textField will be added to the scrollPane. */ protected JScrollPane scrollPane; /** It <code>true</code>, the locale change event is currently being processed. */ protected boolean isLocaleChanging = false; /** It <code>true</code>, <code>locales</code> combobox is currently updating its item order. */ protected boolean isUpdatingLocales = false; /** * @param strings The list of translatable strings. * @param textField The component to be used for typing in the translations. */ public JLangStringListTextField(List<LangString> strings, T textField) { this(strings, textField, null); } /** * @param strings The list of translatable strings. * @param textField The component to be used for typing in the translations. * @param scrollPane If not <code>null</code>, the textField will be added to the scrollPane. */ public JLangStringListTextField(List<LangString> strings, T textField, JScrollPane scrollPane) { if (strings == null) throw new NullPointerException("Cannot create JLangStringListTextField with a null list."); this.strings = strings; this.scrollPane = scrollPane; setLayout(new FormLayout("0px:grow,$lcgap,pref", "pref")); CellConstraints cc = new CellConstraints(); value = textField; if (scrollPane == null) { add(value, cc.xy(1, 1)); } else { scrollPane.setViewportView(value); add(scrollPane, cc.xy(1, 1)); } locales = new JLocaleComboBox(); updateLocales(); add(locales, cc.xy(3, 1)); locales.addPopupMenuListener(new PopupMenuListener() { @Override public void popupMenuWillBecomeVisible(PopupMenuEvent e) { } @Override public void popupMenuWillBecomeInvisible(PopupMenuEvent e) { value.requestFocusInWindow(); } @Override public void popupMenuCanceled(PopupMenuEvent e) { value.requestFocusInWindow(); } }); locales.setAction(new AbstractAction() { /** */ private static final long serialVersionUID = -5477415299077842204L; @Override public void actionPerformed(ActionEvent e) { if (isUpdatingLocales) return; isLocaleChanging = true; Locale l = (Locale) locales.getSelectedItem(); LangString string = getLangString(l); if (string == null) { value.setText(""); } else { value.setText(string.getValue()); } isLocaleChanging = false; } }); locales.setSelectedIndex(0); final DocumentListener textChangeListener = new UniversalDocumentListener() { @Override protected void update(DocumentEvent e) { if (isLocaleChanging) return; LangString string = getLangString((Locale) locales.getSelectedItem()); if (string == null) { string = (LangString) new ObjectFactory().createLangString(); string.setLang((Locale) locales.getSelectedItem()); string.setValue(""); JLangStringListTextField.this.strings.add(string); updateLocales(); } int index = JLangStringListTextField.this.strings.indexOf(string); if (value.getText() != null && value.getText().length() > 0) { string.setValue(value.getText()); JLangStringListTextField.this.strings.set(index, string); // fire a change notification for // observable lists } else { JLangStringListTextField.this.strings.remove(string); updateLocales(); } } }; value.getDocument().addDocumentListener(textChangeListener); value.addPropertyChangeListener("document", new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { value.getDocument().addDocumentListener(textChangeListener); } }); setBorder(BorderFactory.createLineBorder(Color.GRAY, 1)); } /** * @return The locales that should be topped. The default implementation returns the application and diagram locales * from user settings along with all locales for which an entry exists in this input. */ protected LinkedHashSet<Locale> getSuggestedLocales() { LinkedHashSet<Locale> result = new LinkedHashSet<Locale>(2); Configuration conf = ServiceLocator.get(ConfigurationManager.class).get(); boolean isSetForLocale = false, isSetForDiagramLocale = false; Locale locale = conf.getLocale(); for (LangString s : strings) { if (s.getLang().equals(locale)) { isSetForLocale = true; break; } } Locale diagramLocale = conf.getDiagramLocale(); if (diagramLocale.equals(locale)) { isSetForDiagramLocale = isSetForLocale; } else { for (LangString s : strings) { if (s.getLang().equals(diagramLocale)) { isSetForDiagramLocale = true; break; } } } if (isSetForLocale) result.add(conf.getLocale()); if (isSetForDiagramLocale) result.add(conf.getDiagramLocale()); for (LangString s : strings) result.add(s.getLang()); if (!isSetForLocale) result.add(conf.getLocale()); if (!isSetForDiagramLocale) result.add(conf.getDiagramLocale()); return result; } /** * Updates the combobox with locales to have the suggested locales moved to the top. */ protected void updateLocales() { isUpdatingLocales = true; locales.updateSuggested(getSuggestedLocales()); isUpdatingLocales = false; } /** * Return the {@link LangString} from <code>strings</code> for the given {@link Locale}. * * @param l The locale which to find the {@link LangString} for. * @return The {@link LangString} from <code>strings</code> for the given {@link Locale} or <code>null</code> if the * list doesn't contain any {@link LangString} for the given {@link Locale}. */ protected LangString getLangString(Locale l) { for (LangString s : strings) { if (s.getLang().equals(l)) return s; } return null; } /** * @return the strings */ public List<LangString> getStrings() { return strings; } /** * @param strings the strings to set */ public void setStrings(List<LangString> strings) { this.strings = strings; updateLocales(); locales.setSelectedIndex(0); } /** * @return the locales */ public JLocaleComboBox getComboBox() { return locales; } /** * @return the value */ public T getTextField() { return value; } /** * @return the scrollPane */ public JScrollPane getScrollPane() { return scrollPane; } @Override public void setEnabled(boolean enabled) { super.setEnabled(enabled); locales.setEnabled(enabled); value.setEnabled(enabled); if (scrollPane != null) scrollPane.setEnabled(enabled); } }