package com.tom_roush.pdfbox.pdmodel.interactive.form; import com.tom_roush.pdfbox.cos.COSBase; import com.tom_roush.pdfbox.cos.COSDictionary; import com.tom_roush.pdfbox.cos.COSName; import com.tom_roush.pdfbox.pdmodel.interactive.annotation.PDAnnotationWidget; import com.tom_roush.pdfbox.pdmodel.interactive.annotation.PDAppearanceDictionary; import com.tom_roush.pdfbox.pdmodel.interactive.annotation.PDAppearanceEntry; import java.io.IOException; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; /** * Radio button fields contain a set of related buttons that can each be on or off. * * @author sug */ public final class PDRadioButton extends PDButton { /** * An Ff flag. */ private static final int FLAG_NO_TOGGLE_TO_OFF = 1 << 14; /** * @param acroForm The acroform. * @see PDField#PDField(PDAcroForm) */ public PDRadioButton(PDAcroForm acroForm) { super(acroForm); setRadioButton(true); } /** * Constructor. * * @param acroForm The form that this field is part of. * @param field the PDF object to represent as a field. * @param parent the parent node of the node */ PDRadioButton(PDAcroForm acroForm, COSDictionary field, PDNonTerminalField parent) { super(acroForm, field, parent); } /** * From the PDF Spec <br/> * If set, a group of radio buttons within a radio button field that use the same value for the on state will turn * on and off in unison; that is if one is checked, they are all checked. If clear, the buttons are mutually * exclusive (the same behavior as HTML radio buttons). * * @param radiosInUnison The new flag for radiosInUnison. */ public void setRadiosInUnison(boolean radiosInUnison) { dictionary.setFlag(COSName.FF, FLAG_RADIOS_IN_UNISON, radiosInUnison); } /** * @return true If the flag is set for radios in unison. */ public boolean isRadiosInUnison() { return dictionary.getFlag(COSName.FF, FLAG_RADIOS_IN_UNISON); } /** * This will get the selected export values. * <p> * A RadioButton might have an export value to allow field values * which can not be encoded as PDFDocEncoding or for the same export value * being assigned to multiple RadioButtons in a group.<br/> * To define an export value the RadioButton must define options {@link #setExportValues(List)} * which correspond to the individual items within the RadioButton.</p> * <p> * The method will either return the corresponding values from the options entry or in case there * is no such entry the fields value</p> * * @return the export value of the field. * @throws IOException in case the fields value can not be retrieved */ public List<String> getSelectedExportValues() throws IOException { List<String> onValues = getSelectableOnValues(); List<String> exportValues = getExportValues(); List<String> selectedExportValues = new ArrayList<String>(); if (exportValues.isEmpty()) { selectedExportValues.add(getValue()); return selectedExportValues; } else { String fieldValue = getValue(); int idx = 0; for (String onValue : onValues) { if (onValue.compareTo(fieldValue) == 0) { selectedExportValues.add(exportValues.get(idx)); } } return selectedExportValues; } } /** * Returns the selected value. May be empty if NoToggleToOff is set but there is no value * selected. * * @return A non-null string. */ public String getValue() { COSBase value = getInheritableAttribute(COSName.V); if (value instanceof COSName) { return ((COSName) value).getName(); } else { return ""; } } /** * Returns the default value, if any. * * @return A non-null string. */ public String getDefaultValue() { COSBase value = getInheritableAttribute(COSName.DV); if (value instanceof COSName) { return ((COSName) value).getName(); } else { return ""; } } @Override public String getValueAsString() { return getValue(); } /** * Sets the selected radio button, given its name. * * @param value Name of radio button to select * @throws IOException if the value could not be set * @throws IllegalArgumentException if the value is not a valid option. */ public void setValue(String value) throws IOException { checkValue(value); dictionary.setName(COSName.V, value); // update the appearance state (AS) for (PDAnnotationWidget widget : getWidgets()) { PDAppearanceEntry appearanceEntry = widget.getAppearance().getNormalAppearance(); if (((COSDictionary) appearanceEntry.getCOSObject()).containsKey(value)) { widget.getCOSObject().setName(COSName.AS, value); } else { widget.getCOSObject().setItem(COSName.AS, COSName.Off); } } applyChange(); } /** * Sets the default value. * * @param value Name of radio button to select * @throws IOException if the value could not be set * @throws IllegalArgumentException if the value is not a valid option. */ public void setDefaultValue(String value) { checkValue(value); dictionary.setName(COSName.DV, value); } /** * Get the values to set individual radio buttons to the on state. * * <p>The On value could be an arbitrary string as long as it is within the limitations of * a PDF name object. The Off value shall always be 'Off'. If not set or not part of the normal * appearance keys 'Off' is the default</p> * * @return the value setting the check box to the On state. * If an empty string is returned there is no appearance definition. */ public Set<String> getOnValues() { // we need a set as the radio buttons can appear multiple times Set<String> onValues = new HashSet<String>(); onValues.addAll(getSelectableOnValues()); return onValues; } /** * Checks value. * * @param value Name of radio button to select * @throws IllegalArgumentException if the value is not a valid option. */ private void checkValue(String value) throws IllegalArgumentException { Set<String> onValues = getOnValues(); if (COSName.Off.getName().compareTo(value) != 0 && !onValues.contains(value)) { throw new IllegalArgumentException("value '" + value + "' is not a valid option for the radio button " + getFullyQualifiedName() + ", valid values are: " + onValues + " and " + COSName.Off.getName()); } } /** * Get all potential ON values. * * @return the ON values. */ private List<String> getSelectableOnValues() { List<PDAnnotationWidget> widgets = this.getWidgets(); // we need a set as the radio buttons can appear multiple times List<String> onValues = new ArrayList<String>(); for (PDAnnotationWidget widget : widgets) { PDAppearanceDictionary apDictionary = widget.getAppearance(); if (apDictionary != null) { PDAppearanceEntry normalAppearance = apDictionary.getNormalAppearance(); if (normalAppearance != null) { Set<COSName> entries = normalAppearance.getSubDictionary().keySet(); for (COSName entry : entries) { if (COSName.Off.compareTo(entry) != 0) { onValues.add(entry.getName()); } } } } } return onValues; } }