/*
* Copyright 2006-2017 ICEsoft Technologies Canada Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an "AS
* IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
package org.icepdf.core.pobjects.acroform;
import org.icepdf.core.pobjects.Name;
import org.icepdf.core.pobjects.Reference;
import org.icepdf.core.pobjects.StringObject;
import org.icepdf.core.util.Library;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
/**
* The ChoiceFieldDictionary contains all the dictionary entries specific to
* the choice widget.
*
* @since 5.1
*/
public class ChoiceFieldDictionary extends VariableTextFieldDictionary {
/**
* (Optional) An array of options that shall be presented to the user. Each
* element of the array is either a text string representing one of the
* available options or an array consisting of two text strings: the option’s
* export value and the text that shall be displayed as the name of the option.
* <br>
* If this entry is not present, no choices should be presented to the user.
*/
public static final Name OPT_KEY = new Name("Opt");
/**
* (Optional) For scrollable list boxes, the top index (the index in the Opt
* array of the first option visible in the list). Default value: 0.
*/
public static final Name TI_KEY = new Name("TI");
/**
* (Sometimes required, otherwise optional; PDF 1.4) For choice fields that
* allow multiple selection (MultiSelect flag set), an array of integers,
* sorted in ascending order, representing the zero-based indices in the Opt
* array of the currently selected option items. This entry shall be used
* when two or more elements in the Opt array have different names but the
* same export value or when the value of the choice field is an array.
* This entry should not be used for choice fields that do not allow multiple
* selection. If the items identified by this entry differ from those in the
* V entry of the field dictionary (see discussion following this Table),
* the V entry shall be used.
*/
public static final Name I_KEY = new Name("I");
/**
* If set, the field is a combo box; if clear, the field is a list box.
*/
public static final int COMBO_BIT_FLAG = 0x20000;
/**
* If set, the combo box shall include an editable text box as well as a
* drop-down list; if clear, it shall include only a drop-down list. This
* flag shall be used only if the Combo flag is set.
*/
public static final int EDIT_BIT_FLAG = 0x40000;
/**
* If set, the field’s option items shall be sorted alphabetically. This flag
* is intended for use by writers, not by readers. Conforming readers shall
* display the options in the order in which they occur in the Opt array
* (see Table 231).
*/
public static final int SORT_BIT_FLAG = 0x80000;
/**
* (PDF 1.4) If set, more than one of the field’s option items may be selected
* simultaneously; if clear, at most one item shall be selected.
*/
public static final int MULTI_SELECT_BIT_FLAG = 0x200000;
/**
* (PDF 1.4) If set, text entered in the field shall not be spell-checked.
* This flag shall not be used unless the Combo and Edit flags are both set.
*/
public static final int CHOICE_DO_NOT_SPELL_CHECK_BIT_FLAG = 0x400000;
/**
* (PDF 1.5) If set, the new value shall be committed as soon as a selection
* is made (commonly with the pointing device). In this case, supplying a
* value for a field involves three actions: selecting the field for fill-in,
* selecting a choice for the fill-in value, and leaving that field, which
* finalizes or "commits" the data choice and triggers any actions associated
* with the entry or changing of this data. If this flag is on, then processing
* does not wait for leaving the field action to occur, but immediately
* proceeds to the third step.
* <br>
* This option enables applications to perform an action once a selection is
* made, without requiring the user to exit the field. If clear, the new
* value is not committed until the user exits the field.
*/
public static final int COMMIT_ON_SEL_CHANGE_BIT_FLAG = 0x4000000;
public enum ChoiceFieldType {
CHOICE_COMBO, CHOICE_EDITABLE_COMBO,
CHOICE_LIST_SINGLE_SELECT, CHOICE_LIST_MULTIPLE_SELECT
}
protected ChoiceFieldType choiceFieldType;
protected ArrayList<ChoiceOption> options;
protected int topIndex;
protected ArrayList<Integer> indexes;
@SuppressWarnings("unchecked")
public ChoiceFieldDictionary(Library library, HashMap entries) {
super(library, entries);
// options/list times.
org.icepdf.core.pobjects.security.SecurityManager securityManager = library.getSecurityManager();
Object value = library.getArray(entries, OPT_KEY);
if (value == null) {
FieldDictionary parent = getParent();
value = library.getArray(parent.getEntries(), OPT_KEY);
}
if (value != null) {
ArrayList opts = (ArrayList) value;
options = new ArrayList<ChoiceOption>(opts.size());
for (Object opt : opts) {
if (opt instanceof StringObject) {
StringObject tmp = (StringObject) opt;
String tmpString = tmp.getDecryptedLiteralString(securityManager);
options.add(new ChoiceOption(tmpString, tmpString));
} else if (opt instanceof List) {
List tmp = (List) opt;
StringObject tmp1StingObject = (StringObject) tmp.get(0);
String tmpString1 = tmp1StingObject.getDecryptedLiteralString(securityManager);
StringObject tmp2StingObject = (StringObject) tmp.get(1);
String tmpString2 = tmp2StingObject.getDecryptedLiteralString(securityManager);
options.add(new ChoiceOption(tmpString1, tmpString2));
}
}
} else {
options = null;
}
// determine combo or list
int flags = getFlags();
if ((flags & COMBO_BIT_FLAG) ==
COMBO_BIT_FLAG) {
// check for editable
if ((flags & EDIT_BIT_FLAG) ==
EDIT_BIT_FLAG) {
choiceFieldType = ChoiceFieldType.CHOICE_EDITABLE_COMBO;
} else {
choiceFieldType = ChoiceFieldType.CHOICE_COMBO;
}
} else {
// check for selection mode
if ((flags & MULTI_SELECT_BIT_FLAG) ==
MULTI_SELECT_BIT_FLAG) {
choiceFieldType = ChoiceFieldType.CHOICE_LIST_MULTIPLE_SELECT;
} else {
choiceFieldType = ChoiceFieldType.CHOICE_LIST_SINGLE_SELECT;
}
}
// select the selected index.
if (choiceFieldType == ChoiceFieldType.CHOICE_LIST_SINGLE_SELECT) {
value = library.getObject(entries, TI_KEY);
if (value instanceof Number) {
topIndex = ((Number) value).intValue();
}
}
value = library.getObject(entries, I_KEY);
if (value instanceof ArrayList) {
ArrayList<Number> tmp = (ArrayList) value;
indexes = new ArrayList<Integer>(tmp.size());
for (Number aTmp : tmp) {
indexes.add(aTmp.intValue());
}
}
// we might not have an I_key but should have a value to work with if so we build the index our self.
if (indexes == null && options != null) {
indexes = new ArrayList<Integer>(1);
for (int i = 0, j = 0, max = options.size(); i < max; i++) {
if (options.get(i).getLabel().equals(value)) {
indexes.set(j, i);
j++;
}
}
}
}
/**
* Regular field value writing takes place as well as the update of the I (indexes) entry in the dictionary.
* TODO: Further work is needed to fully support multiSelect values.
* @param fieldValue value to write.
* @param parentReference parent reference.
*/
@Override
public void setFieldValue(Object fieldValue, Reference parentReference) {
// update the index to reflect the change,
String selectedValue = null;
if (fieldValue instanceof String) {
selectedValue = (String) fieldValue;
super.setFieldValue(selectedValue, parentReference);
} else if (fieldValue instanceof StringObject) {
StringObject tmp = (StringObject) fieldValue;
selectedValue = tmp.getDecryptedLiteralString(library.getSecurityManager());
super.setFieldValue(selectedValue, parentReference);
}else if (fieldValue instanceof ChoiceOption) {
ChoiceOption tmp = (ChoiceOption) fieldValue;
selectedValue = tmp.getValue();
super.setFieldValue(selectedValue, parentReference);
}
if (indexes != null) {
indexes.clear();
}else{
indexes = new ArrayList<Integer>();
}
for (int i = 0, j = 0, max = options.size(); i < max; i++) {
if (options.get(i).getLabel().equals(selectedValue)) {
indexes.add(j, i);
}
}
indexes.trimToSize();
// store the new indexes in the dictionary.
entries.put(I_KEY, indexes);
}
public ChoiceOption buildChoiceOption(String label, String value) {
return new ChoiceOption(label, value);
}
public ChoiceFieldType getChoiceFieldType() {
return choiceFieldType;
}
/**
* For scrollable list boxes, the top index (the index in the Opt array of the first option visible in the list).
* Default value: 0.
*
* @return the top index of a scrollable list boxes.
*/
public int getTopIndex() {
return topIndex;
}
public void setTopIndex(int topIndex) {
this.topIndex = topIndex;
}
public ArrayList<Integer> getIndexes() {
return indexes;
}
/**
* For choice fields that allow multiple selection (MultiSelect flag set), an array of integers, sorted in
* ascending order, representing the zero-based indices in the Opt array of the currently selected option items.
* This entry shall be used when two or more elements in the Opt array have different names but the same export
* value or when the value of the choice field is an array. This entry should not be used for choice fields that
* do not allow multiple selection. If the items identified by this entry differ from those in the V entry of the
* field dictionary (see discussion following this Table), the V entry shall be used.
*
* @param indexes list of selected indexes for multiple selection.
*/
public void setIndexes(ArrayList<Integer> indexes) {
this.indexes = indexes;
}
/**
* An array of options that shall be presented to the user. Each element of the array is either a text
* string representing one of the available options or an array consisting of two text strings: the option’s
* export value and the text that shall be displayed as the name of the option.
* <br>
* If this entry is not present, no choices should be presented to the user.
*
* @return choice options.
*/
public ArrayList<ChoiceOption> getOptions() {
return options;
}
public void setOptions(ArrayList<ChoiceOption> options) {
this.options = options;
}
public class ChoiceOption {
private String label;
private String value;
private boolean isSelected;
public ChoiceOption(String label, String value) {
this.label = label;
this.value = value;
}
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
@Override
public String toString() {
return label;
}
public boolean isSelected() {
return isSelected;
}
public void setIsSelected(boolean isSelected) {
this.isSelected = isSelected;
}
}
/**
* If set, the field’s option items shall be sorted alphabetically. This flag is intended for use by
* writers, not by readers. Conforming readers shall display the options in the order in which they
* occur in the Opt array.
*
* @return true if field items are to be sorted.
*/
public boolean isSortFields() {
return (getFlags() & SORT_BIT_FLAG) == SORT_BIT_FLAG;
}
/**
* If set, more than one of the field’s option items may be selected simultaneously; if clear, at most
* one item shall be selected.
*
* @return true if more then one field can be selected, otherwise false.
*/
public boolean isMultiSelect() {
return (getFlags() & MULTI_SELECT_BIT_FLAG) == MULTI_SELECT_BIT_FLAG;
}
/**
* If set, the new value shall be committed as soon as a selection is made (commonly with the pointing device).
* In this case, supplying a value for a field involves three actions: selecting the field for fill-in, selecting
* a choice for the fill-in value, and leaving that field, which finalizes or "commits" the data choice and triggers
* any actions associated with the entry or changing of this data. If this flag is on, then processing does not wait
* for leaving the field action to occur, but immediately proceeds to the third step.
* <br>
* This option enables applications to perform an action once a selection is made, without requiring the user to
* exit the field. If clear, the new value is not committed until the user exits the field.
*
* @return true if commit on set change, otherwise false.
*/
public boolean isCommitOnSetChange() {
return (getFlags() & COMMIT_ON_SEL_CHANGE_BIT_FLAG) == COMMIT_ON_SEL_CHANGE_BIT_FLAG;
}
}