package com.kenai.redminenb.issue;
import com.kenai.redminenb.util.DelegatingBaseLineJPanel;
import com.kenai.redminenb.util.markup.StringUtil;
import com.taskadapter.redmineapi.bean.CustomFieldDefinition;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.logging.Logger;
import javax.swing.DefaultComboBoxModel;
import javax.swing.DefaultListModel;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.ListModel;
import javax.swing.ListSelectionModel;
import org.jdesktop.swingx.JXDatePicker;
import org.jdesktop.swingx.WrapLayout;
public abstract class CustomFieldComponent extends DelegatingBaseLineJPanel implements ActionListener {
private static final Logger LOG = Logger.getLogger(CustomFieldComponent.class.getName());
private enum Type {
display_only,
bool_field,
date_field,
float_field,
int_field,
link_field,
list_field,
text_field,
string_field,
user_field,
version_field
}
public static CustomFieldComponent create(CustomFieldDefinition cfd) {
String type = cfd.getFieldFormat();
Type resolvedType = Type.display_only;
try {
resolvedType = Type.valueOf(type + "_field");
} catch (IllegalArgumentException ex) {
LOG.info("Failed to resolve type: " + type);
}
switch (resolvedType) {
case date_field:
return new CustomFieldComponentDate(cfd);
case string_field:
case link_field:
return new CustomFieldComponentLine(cfd);
case text_field:
return new CustomFieldComponentLongText(cfd);
case list_field:
return new CustomFieldComponentList(cfd);
case bool_field:
return new CustomFieldComponentBool(cfd);
case int_field:
return new CustomFieldComponentNumeric(cfd, false);
case float_field:
return new CustomFieldComponentNumeric(cfd, true);
case version_field:
case user_field:
return new CustomFieldComponentListId(cfd);
default:
return new CustomFieldComponentDisplay(cfd, resolvedType);
}
}
private final CustomFieldDefinition cfd;
private JLabel label;
protected JPopupMenu popup = new JPopupMenu();
private CustomFieldComponent(CustomFieldDefinition cfd) {
this.cfd = cfd;
setOpaque(false);
JMenuItem mi = new JMenuItem("Reset");
mi.addActionListener(this);
mi.setActionCommand("reset");
popup.add(mi);
this.setComponentPopupMenu(popup);
}
abstract public String getValue();
abstract public void setValue(String value);
abstract public void setValues(List<String> values);
abstract public List<String> getValues();
public CustomFieldDefinition getCustomFieldDefinition() {
return cfd;
}
public void setDefaultValue() {
setValue(cfd.getDefaultValue());
}
public JLabel getLabel() {
if (label == null) {
label = new JLabel(cfd.getName() + ":");
}
return label;
}
@Override
public void actionPerformed(ActionEvent e) {
switch (e.getActionCommand()) {
case "reset":
this.setDefaultValue();
break;
default:
assert false : "Unhandled action command";
break;
}
}
private static class CustomFieldComponentDisplay extends CustomFieldComponent {
private final List<String> values = new ArrayList<>();
private final JLabel outputLabel;
private final Type type;
public CustomFieldComponentDisplay(CustomFieldDefinition cfdd, Type type) {
super(cfdd);
this.type = type;
this.outputLabel = new JLabel();
this.setLayout(new BorderLayout());
this.add(outputLabel);
this.setComponentPopupMenu(null);
}
@Override
public void setValue(String value) {
values.clear();
values.add(value);
this.toDisplay();
}
@Override
public java.lang.String getValue() {
if(values.size() > 0) {
return values.get(0);
} else {
return null;
}
}
@Override
public void setValues(List<String> values) {
values.clear();
values.addAll(values);
this.toDisplay();
}
@Override
public List<String> getValues() {
return Collections.unmodifiableList(values);
}
private void toDisplay() {
StringBuilder sb = new StringBuilder();
switch (type) {
case bool_field:
switch (getValue()) {
case "0":
sb.append("No");
break;
case "1":
sb.append("Yes");
break;
default:
sb.append("");
break;
}
break;
case user_field:
case version_field:
List<String> ids = new ArrayList<>();
if (getCustomFieldDefinition().isMultiple()) {
for (String value : getValues()) {
ids.add(value);
}
} else {
ids.add(getValue());
}
for(String id: ids) {
for(String possibleValue: getCustomFieldDefinition().getPossibleValues()) {
if (possibleValue.endsWith("[" + id + "]")) {
if (sb.length() != 0) {
sb.append("\n");
}
sb.append(possibleValue);
break;
}
}
}
break;
case list_field:
case date_field:
case int_field:
case float_field:
case link_field:
case text_field:
case string_field:
case display_only:
default:
if (getCustomFieldDefinition().isMultiple()) {
for (String value : getValues()) {
if (sb.length() != 0) {
sb.append("\n");
}
sb.append(value);
}
} else {
sb.append(getValue());
}
break;
}
outputLabel.setText("<html>" + StringUtil.escapeHTML(sb.toString()));
}
}
private static class CustomFieldComponentDate extends CustomFieldComponent {
private static final SimpleDateFormat isoDate = new SimpleDateFormat("yyyy-MM-dd");
private final JXDatePicker datePicker = new JXDatePicker();
public CustomFieldComponentDate(CustomFieldDefinition cfdd) {
super(cfdd);
this.setLayout(new WrapLayout(WrapLayout.LEADING));
this.add(datePicker);
datePicker.setComponentPopupMenu(popup);
}
@Override
public void setValue(String value) {
synchronized (isoDate) {
try {
Date d = isoDate.parse(value);
datePicker.setDate(d);
} catch (Exception ex) {
datePicker.setDate(null);
}
}
}
@Override
public java.lang.String getValue() {
if (datePicker.getDate() != null) {
synchronized(isoDate) {
return isoDate.format(datePicker.getDate());
}
} else {
return null;
}
}
@Override
public void setValues(List<String> values) {
throw new UnsupportedOperationException("Method not supported for type");
}
@Override
public List<String> getValues() {
throw new UnsupportedOperationException("Method not supported for type");
}
}
private static class CustomFieldComponentLine extends CustomFieldComponent {
private final JTextField line = new JTextField();
public CustomFieldComponentLine(CustomFieldDefinition cfdd) {
super(cfdd);
this.setLayout(new BorderLayout());
this.add(line);
line.setComponentPopupMenu(popup);
}
@Override
public void setValue(String value) {
if(value == null) {
value = "";
}
line.setText(value);
}
@Override
public java.lang.String getValue() {
return line.getText();
}
@Override
public void setValues(List<String> values) {
throw new UnsupportedOperationException("Method not supported for type");
}
@Override
public List<String> getValues() {
throw new UnsupportedOperationException("Method not supported for type");
}
}
private static class CustomFieldComponentList extends CustomFieldComponent {
private final JList<String> list = new JList<>();
public CustomFieldComponentList(CustomFieldDefinition cfd) {
super(cfd);
this.setLayout(new BorderLayout());
this.add(new JScrollPane(list));
this.setPreferredSize(new Dimension(0, 75));
DefaultListModel<String> dlm = new DefaultListModel<>();
List<String> possibleValues = cfd.getPossibleValues();
for(int i = 0; i < possibleValues.size(); i++) {
dlm.add(i, possibleValues.get(i));
}
list.setModel(dlm);
if(cfd.isMultiple()) {
list.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
} else {
list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
}
list.setComponentPopupMenu(popup);
}
@Override
public void setValue(String value) {
list.setSelectedValue(value, true);
}
@Override
public java.lang.String getValue() {
return list.getSelectedValue();
}
@Override
public void setValues(List<String> selectedValues) {
List<Integer> selectIndices = new ArrayList<>();
ListModel<String> lm = list.getModel();
for (int i = 0; i < lm.getSize(); i++) {
if (selectedValues.contains(lm.getElementAt(i))) {
selectIndices.add(i);
}
}
int selectedIndicesArray[] = new int[selectIndices.size()];
for (int i = 0; i < selectIndices.size(); i++) {
selectedIndicesArray[i] = selectIndices.get(i);
}
list.setSelectedIndices(selectedIndicesArray);
}
@Override
public List<String> getValues() {
List<String> result = new ArrayList<>(list.getSelectedIndices().length);
for (int index : list.getSelectedIndices()) {
result.add(list.getModel().getElementAt(index));
}
return result;
}
}
private static class CustomFieldComponentListId extends CustomFieldComponent {
private final DefaultListModel<String> dlm = new DefaultListModel<>();
private final JList<String> list = new JList<>(dlm);
public CustomFieldComponentListId(CustomFieldDefinition cfd) {
super(cfd);
this.setLayout(new BorderLayout());
this.add(new JScrollPane(list));
this.setPreferredSize(new Dimension(0, 75));
List<String> possibleValues = cfd.getPossibleValues();
for (int i = 0; i < possibleValues.size(); i++) {
dlm.add(i, possibleValues.get(i));
}
if (cfd.isMultiple()) {
list.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
} else {
list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
}
list.setComponentPopupMenu(popup);
}
@Override
public void setValue(String id) {
list.clearSelection();
for (int i = 0; i < dlm.getSize(); i++) {
String candidate = dlm.getElementAt(i);
if (candidate.endsWith("[" + id + "]")) {
list.setSelectedIndex(i);
break;
}
}
}
@Override
public java.lang.String getValue() {
String result = list.getSelectedValue();
if(result != null) {
result = extractId(result);
}
return result;
}
@Override
public void setValues(List<String> selectedValues) {
List<Integer> selectIndices = new ArrayList<>();
for (int i = 0; i < dlm.getSize(); i++) {
String candidate = dlm.getElementAt(i);
for (String id : selectedValues) {
if (candidate.endsWith("[" + id + "]")) {
selectIndices.add(i);
break;
}
}
}
int selectedIndicesArray[] = new int[selectIndices.size()];
for (int i = 0; i < selectIndices.size(); i++) {
selectedIndicesArray[i] = selectIndices.get(i);
}
list.setSelectedIndices(selectedIndicesArray);
}
@Override
public List<String> getValues() {
List<String> result = new ArrayList<>(list.getSelectedIndices().length);
for (int index : list.getSelectedIndices()) {
String value = dlm.getElementAt(index);
result.add(extractId(value));
}
return result;
}
private String extractId(String input) {
return input.substring(input.lastIndexOf('[') + 1, input.lastIndexOf(']'));
}
}
private static class CustomFieldComponentLongText extends CustomFieldComponent {
private final JTextArea textarea = new JTextArea();
public CustomFieldComponentLongText(CustomFieldDefinition cfd) {
super(cfd);
this.setLayout(new BorderLayout());
this.add(new JScrollPane(textarea));
this.setPreferredSize(new Dimension(0, 75));
textarea.setComponentPopupMenu(popup);
}
@Override
public void setValue(String value) {
textarea.setText(value);
}
@Override
public java.lang.String getValue() {
return textarea.getText();
}
@Override
public void setValues(List<String> values) {
throw new UnsupportedOperationException("Method not supported for type");
}
@Override
public List<String> getValues() {
throw new UnsupportedOperationException("Method not supported for type");
}
}
private static class CustomFieldComponentBool extends CustomFieldComponent {
private final JComboBox<String> combo = new JComboBox<>();
public CustomFieldComponentBool(CustomFieldDefinition cfdd) {
super(cfdd);
this.setLayout(new WrapLayout(WrapLayout.LEADING));
DefaultComboBoxModel<String> dcbm = new DefaultComboBoxModel<>();
dcbm.addElement(" ");
dcbm.addElement("Yes");
dcbm.addElement("No");
combo.setModel(dcbm);
this.add(combo);
}
@Override
public void setValue(String value) {
if("1".equals(value)) {
combo.setSelectedIndex(1);
} else if ("0".equals(value)) {
combo.setSelectedIndex(2);
} else {
combo.setSelectedIndex(0);
}
}
@Override
public java.lang.String getValue() {
switch(combo.getSelectedIndex()) {
case 1:
return "1";
case 2:
return "0";
default:
return null;
}
}
@Override
public void setValues(List<String> values) {
throw new UnsupportedOperationException("Method not supported for type");
}
@Override
public List<String> getValues() {
throw new UnsupportedOperationException("Method not supported for type");
}
}
private static class CustomFieldComponentNumeric extends CustomFieldComponent implements FocusListener {
private final JTextField line = new JTextField();
private final boolean floating;
private String oldValue = null;
public CustomFieldComponentNumeric(CustomFieldDefinition cfd, boolean floating) {
super(cfd);
this.setLayout(new BorderLayout());
this.add(line);
this.floating = floating;
line.setComponentPopupMenu(popup);
}
@Override
public void setValue(String value) {
if(value == null) {
value = "";
}
line.setText(value);
}
@Override
public java.lang.String getValue() {
String text = line.getText();
if(text.isEmpty()) {
return null;
} else {
return text;
}
}
@Override
public void setValues(List<String> values) {
throw new UnsupportedOperationException("Method not supported for type");
}
@Override
public List<String> getValues() {
throw new UnsupportedOperationException("Method not supported for type");
}
@Override
public void focusGained(FocusEvent e) {
oldValue = line.getText();
}
@Override
public void focusLost(FocusEvent e) {
String newValue = line.getText();
if("".equals(newValue)) {
return;
}
try {
if (floating) {
Double.parseDouble(newValue);
} else {
Integer.parseInt(newValue);
}
} catch (NumberFormatException ne) {
line.setText(oldValue);
}
}
}
}