package com.gmail.dpierron.calibre.gui;
/**
* Created by WalkerDJ on 19/04/2014.
*
* This class describes a single field within the GUI.
*
* It purpose is to allo some generalisation of the code to do with tranlating
* the user interface, and loading and storing configuration settings.
*
* It uses the Java reflection API to allow method names to be derived from strings. Since
* for all properties we have a get, set and isReadOnly method we can store just the base name.
*/
import com.gmail.dpierron.calibre.configuration.ConfigurationHolder;
import com.gmail.dpierron.calibre.configuration.ConfigurationManager;
import com.gmail.dpierron.tools.i18n.Localization;
import com.gmail.dpierron.tools.Helper;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.File;
import javax.swing.*;
import java.lang.reflect.*;
import java.util.Locale;
public class guiField {
private final static Logger logger = LogManager.getLogger(guiField.class);
private JComponent guiLabelField; // Label associated with this field
private JComponent guiValueField; // Value field associated with this field. NULL if no value involved
private String localizationKey; // LocalisationText (without .lable/.tooltip)
private String methodBase; // The base of the methodNames associated with loading/storing values
// Set it to null if this field does not have a value loaded/stored
boolean negate; // For check boxes, set to true if checkbox value is negated when displayed
int minimum, maximum; // Set for numeric fields to indicate range. If both zero then field is not nu
// The following values are cached to reduce the overhead of deriving them each time
private static ConfigurationHolder currentConfiguration = null;
private Class paramTypes[] = new Class[1];
private Object profileType = null;
private Object guiType = null;
//
// CONSTRUCTORS
// Constructor for checkbox fields that have associated configuration information
public guiField (JComponent label, JComponent value, String localizationKey, String methodBase, int minimum, int maximum) {
initialise(label,value, localizationKey, methodBase, false, minimum,maximum);
}
// Constructor for checkbox fields that have associated configuration information
public guiField (JComponent label, JComponent value, String localizationKey, String methodBase, boolean negate) {
initialise(label,value, localizationKey, methodBase, negate, 0,0);
}
// Constructor for none-checkbox fields that have associated configuration information
public guiField (JComponent label, JComponent value, String localizationKey, String methodBase) {
initialise(label,value, localizationKey, methodBase, false, 0,0);
}
// Constructor for fields that have no associated configuration information
public guiField (JComponent label, JComponent value, String localizationKey) {
initialise(label,value, localizationKey, null, false, 0,0);
}
// METHODS
private void initialise(JComponent label, JComponent value, String localizationKey, String methodBase,boolean negate, int minimum, int maximum) {
this.guiLabelField = label;
this.guiValueField = value;
this.localizationKey = localizationKey;
this.methodBase = methodBase;
this.negate = negate;
this.minimum = minimum;
this.maximum = maximum;
}
public void setCurrentConfiguration(ConfigurationHolder c) {
currentConfiguration = c;
}
/**
* Apply the localization for the given field(s)
*/
public void translateTexts () {
String labelText = Localization.Main.getText(localizationKey + ".label");
// Allow for cases where keyndoes not end in .label
if (labelText.endsWith("label")) labelText = Localization.Main.getText(localizationKey);
if (labelText.equals(localizationKey)) labelText = "";
String tooltipText = Localization.Main.getText(localizationKey + ".tooltip");
// Allow for tooltips not lways being supplied
if (tooltipText.endsWith(".tooltip")) tooltipText = "";
// Apply localisation to the designated 'label' field
if (guiLabelField != null) {
if (guiLabelField instanceof JButton) {
((JButton) guiLabelField).setText(labelText);
} else if (guiLabelField instanceof JLabel) {
((JLabel) guiLabelField).setText(labelText);
} else if (guiLabelField instanceof JTextField) {
((JTextField) guiLabelField).setText(labelText);
} else if (guiLabelField instanceof JCheckBox) {
((JCheckBox) guiLabelField).setText(labelText);
} else if (guiLabelField instanceof JTabbedPane) {
int tabIndex = -1 + (localizationKey.charAt(localizationKey.length() - 1)) - '0';
((JTabbedPane) guiLabelField).setTitleAt(tabIndex, labelText);
} else if (guiLabelField instanceof JMenuItem) {
((JMenuItem) guiLabelField).setText(labelText);
} else {
logger.error("setTranslateTexta: Cannot handle the type for LocalizationKey=" + localizationKey);
return;
}
}
// If tooltip provided apply tooltip localisation
if (Helper.isNotNullOrEmpty(tooltipText)) {
if (guiLabelField != null) guiLabelField.setToolTipText(tooltipText);
if (guiValueField != null) guiValueField.setToolTipText(tooltipText);
}
}
/**
* Get the label field associated with this instance
*
* @return
*/
public JComponent getGuiLabel() {
return guiLabelField;
}
public int getMinimum() {
return minimum;
}
public int getMaximum () {
return maximum;
}
private Integer getValue(String s) {
try {
int i = Integer.parseInt(s);
return i;
} catch (NumberFormatException e) {
return -1;
}
}
/**
* Method that gets the value from the GUI and
* returns it as an object of the correct type to
* compare against a profile object.
*
* @return
*/
public Object getGuiValue() {
if (methodBase == null) {
return null;
}
Object guiValue = null;
// Text fields
if (guiValueField instanceof JTextField) {
String s = ((JTextField) guiValueField).getText();
assert s != null : "getGuiValue: Unexpected null return reading value of " + guiValueField.getName();
if (minimum == 0 && maximum == 0) {
if (String.class.equals(profileType)) {
guiValue = s;
} else if (File.class.equals(profileType)) {
if (s.length() > 0) guiValue = new File(s);
} else if (Integer.class.equals (profileType)) {
guiValue = getValue(s);
} else if (Locale.class.equals(profileType)) {
guiValue = s.length() == 0 ? "en" : s;
} else {
logger.error("getGuiValue: Unexpected profileType for 'get" + methodBase + "', type=" + profileType.getClass().getName());
guiValue = null;
}
} else {
assert (Integer.class.equals(profileType));
guiValue = getValue(s);
}
// Checkboxes
} else if (guiValueField instanceof JCheckBox) {
Boolean b = ((JCheckBox) guiValueField).isSelected();
guiValue = negate ? !b : b;
// Combo boxes
} else if (guiValueField instanceof JComboBox) {
guiValue = ((JComboBox) guiValueField).getSelectedItem();
} else {
logger.error("getGuiValue: object type (" + guiValueField.getClass().getName() + ") not recognised for " + guiValueField.getName());
guiValue = null;
}
// For language fields we need a locale object
if (guiValue != null && Locale.class.equals(profileType)) {
assert String.class.equals(guiValue.getClass()) : "Unexpected class " + guiValue.getClass().getName() +" (" + guiValue + ")";
guiValue = Helper.getLocaleFromLanguageString((String)guiValue);
}
return guiValue;
}
/**
* Transfer the value to the correct GUI field
*
* @param oValue
*/
private void putGuiValue (Object oValue) {
if (oValue == null) {
if (profileType instanceof File) {
oValue="";
}
}
// Text fields
if (guiValueField instanceof JTextField) {
String s;
if (oValue instanceof String) {
s = (String)oValue;
} else if (oValue instanceof Integer) {
s = ((Integer)oValue).toString();
} else if (oValue instanceof File) {
s = ((File)oValue).getAbsolutePath();
} else if (oValue instanceof Locale) {
s = ((Locale)oValue).getLanguage();
} else {
// We assume an empty string for null being returned
s = "";
}
// If parameter exceeds maximum, then coerce to the maximum value
if (minimum != 0 || maximum != 0) {
if (Integer.parseInt(s) > maximum) {
s = Integer.toString(maximum);
}
InputVerifier iv = new InputVerifier() {
@Override
public boolean verify(JComponent input) {
if (!(input instanceof JTextField))
return false;
try {
Integer.parseInt(((JTextField) input).getText());
return true;
} catch (NumberFormatException e) {
return false;
}
}
};
((JTextField) guiValueField).setInputVerifier(iv);
}
((JTextField) guiValueField).setText(s);
// Check boxes
} else if (guiValueField instanceof JCheckBox) {
assert oValue instanceof Boolean;
((JCheckBox)guiValueField).setSelected(negate ? !(Boolean)oValue : (Boolean)oValue);
} else if (guiValueField instanceof JComboBox) {
if (oValue instanceof Locale) {
((JComboBox) guiValueField).setSelectedItem(((Locale) oValue).getLanguage());
} else {
((JComboBox) guiValueField).setSelectedItem((String) oValue);
}
} else {
logger.error("loadValue: putGuiValue oBject type not recognised for " + guiLabelField);
return;
}
}
/**
* Method that gets the correct profile value
*
* @return
*/
public Object getProfileValue() {
Method profileGetMethod = getProfileGetMethod();
if (profileGetMethod == null) {
return null;
}
Object profileValue;
try {
// Invoke the get method
profileValue = profileGetMethod.invoke(ConfigurationManager.getCurrentProfile());
} catch (Exception e) {
logger.error("loadValue: Invoke 'get" + methodBase + "' Exception " + e);
profileValue = null;
}
return profileValue;
}
/**
* Store a value in the profile
*
* @param setValue
*/
private void putProfileValue(Object setValue) {
Method profileSetMethod = getProfileSetMethod();
if (profileSetMethod != null) {
try {
profileSetMethod.invoke(ConfigurationManager.getCurrentProfile(), setValue);
// None of the following should happen except in development - but lets play safe!
} catch (final Exception e) {
logger.error("storeValue: invoke 'set" + methodBase + "Excepion " + e);
}
}
}
/**
* Transfer a value from the GUI to the profile
* Do nothing if field has no associated value field or method
*/
public void storeValue() {
if (Helper.isNullOrEmpty(methodBase)) {
// logger.trace("storeValue: Method baee name to store value not set");
return;
}
if (Helper.isNullOrEmpty(guiValueField)) {
// logger.trace("storeValue: Attempt to store a null value");
return;
}
// Now we can set up the parameter for the setmethod
Object setValue = getGuiValue();
if (setValue == null && ! (profileType instanceof File)) {
return;
}
putProfileValue(setValue);
}
/**
* Transfer a value from the profile to the GUI
* Do nothing if field has no associated value field or method
*/
public void loadValue() {
// Check if there is a GUI field to store any value
if (Helper.isNullOrEmpty(guiValueField)) {
return;
}
// Check if we have a profile method base specified
if (Helper.isNullOrEmpty(methodBase)) {
return;
}
// Check we can actually find the method
Method method = getProfileGetMethod();;
if (method == null) {
return;
}
// Get the value from the profile
// Only time null is valid for File objects
Object loadValue = getProfileValue();
putGuiValue(loadValue);
// Now use ReadOnly method to see if field should be currently dsiabled
// If no such method eists, then assume is always enabled
Boolean disable;
try {
String methodName = "is" + methodBase + "ReadOnly";
method = currentConfiguration.getClass().getDeclaredMethod(methodName);
disable = (Boolean) method.invoke(currentConfiguration);
} catch (final Exception e) {
// We assume ReadOnly method does not exist if we get to this point.
disable = false;
}
if (guiLabelField != null) guiLabelField.setEnabled(!disable);
if (guiValueField != null) guiValueField.setEnabled(!disable);
}
/**
* Get the method for getting a value from the profile
*
* If successful will also set up the global variables
* paramTypes[]
* profileType
* This means you MUST have found getProfileGetMethod() before using these
*
* @return method if OK, and null otherwise
*/
private Method getProfileGetMethod() {
Method profileGetMethod = null;
String getMethodName = "get" + methodBase;
try {
// Use get method to work out parameter type for set method
profileGetMethod = currentConfiguration.getClass().getDeclaredMethod(getMethodName);
paramTypes[0] = profileGetMethod.getReturnType();
profileType = profileGetMethod.getReturnType();
} catch (NoSuchMethodException e1) {
logger.error("getProfileGetMethod: unable to find method '" + getMethodName + "'");
profileGetMethod = null;
paramTypes = null;
profileType = null;
}
return profileGetMethod;
}
/**
* Get the method for storing values in the profile.
*
* @return method if successful, null otherwise
*/
private Method getProfileSetMethod() {
String setMethodName = "set" + methodBase;
Method profileSetMethod = null;
try {
profileSetMethod = currentConfiguration.getClass().getDeclaredMethod(setMethodName, paramTypes);
} catch (NoSuchMethodException e1) {
logger.error("getProfileSetMethod: unable to find method '" + setMethodName + "'");
profileSetMethod = null;
}
return profileSetMethod;
}
}