package org.jabref.gui.contentselector; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.Window; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.FocusAdapter; import java.awt.event.FocusEvent; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.BorderFactory; import javax.swing.DefaultListModel; import javax.swing.JButton; import javax.swing.JList; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTextField; import org.jabref.gui.BasePanel; import org.jabref.gui.JabRefDialog; import org.jabref.gui.JabRefFrame; import org.jabref.gui.help.HelpAction; import org.jabref.gui.keyboard.KeyBinder; import org.jabref.logic.help.HelpFile; import org.jabref.logic.l10n.Localization; import org.jabref.model.entry.FieldName; import org.jabref.model.metadata.ContentSelector; import org.jabref.model.metadata.ContentSelectors; import org.jabref.model.metadata.MetaData; import com.jgoodies.forms.builder.ButtonBarBuilder; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; public class ContentSelectorDialog extends JabRefDialog { private static final String WORD_FIRSTLINE_TEXT = Localization.lang("<select word>"); private static final String FIELD_FIRST_LINE = Localization.lang("<field name>"); private static final Log LOGGER = LogFactory.getLog(ContentSelectorDialog.class); private final GridBagLayout gbl = new GridBagLayout(); private final GridBagConstraints con = new GridBagConstraints(); private final JPanel fieldPan = new JPanel(); private final JPanel wordPan = new JPanel(); private final JPanel buttonPan = new JPanel(); private final JPanel fieldNamePan = new JPanel(); private final JPanel wordEditPan = new JPanel(); private final MetaData metaData; private String currentField; private final JabRefFrame frame; private final BasePanel panel; private final JButton newField = new JButton(Localization.lang("New")); private final JButton removeField = new JButton(Localization.lang("Remove")); private final JButton newWord = new JButton(Localization.lang("New")); private final JButton removeWord = new JButton(Localization.lang("Remove")); private final JButton ok = new JButton(Localization.lang("OK")); private final JButton cancel = new JButton(); private final JButton apply = new JButton(Localization.lang("Apply")); private final DefaultListModel<String> fieldListModel = new DefaultListModel<>(); private DefaultListModel<String> wordListModel = new DefaultListModel<>(); private final JList<String> fieldList = new JList<>(fieldListModel); private final JList<String> wordList = new JList<>(wordListModel); private final JTextField fieldNameField = new JTextField("", 20); private final JTextField wordEditField = new JTextField("", 20); private final JScrollPane fPane = new JScrollPane(fieldList); private final JScrollPane wPane = new JScrollPane(wordList); private final Map<String, DefaultListModel<String>> wordListModels = new HashMap<>(); private final List<String> removedFields = new ArrayList<>(); /** * * @param owner the parent Window (Dialog or Frame) * @param frame the JabRef Frame * @param panel the currently selected BasePanel * @param modal should this dialog be modal? * @param fieldName the field this selector is initialized for. May be null. */ public ContentSelectorDialog(Window owner, JabRefFrame frame, BasePanel panel, boolean modal, String fieldName) { super(owner, Localization.lang("Manage content selectors"), ContentSelectorDialog.class); this.setModal(modal); this.metaData = panel.getBibDatabaseContext().getMetaData(); this.frame = frame; this.panel = panel; this.currentField = fieldName; initLayout(); setupFieldSelector(); if (currentField != null) { int fieldInd = fieldListModel.indexOf(currentField); if (fieldInd >= 0) { fieldList.setSelectedIndex(fieldInd); } } else { if (!fieldListModel.isEmpty()) { fieldList.setSelectedIndex(0); currentField = fieldList.getSelectedValue(); } } setupWordSelector(); setupActions(); KeyBinder.bindCloseDialogKeyToCancelAction(this.rootPane, cancel.getAction()); pack(); } private void setupActions() { wordList.addListSelectionListener(e -> { wordEditField.setText(wordList.getSelectedValue()); wordEditField.selectAll(); wordEditField.requestFocus(); }); newWord.addActionListener(e -> newWordAction()); ActionListener wordEditFieldListener = e -> actOnWordEdit(); wordEditField.addActionListener(wordEditFieldListener); removeWord.addActionListener(e -> { int index = wordList.getSelectedIndex(); if (index == -1) { return; } wordListModel.remove(index); wordEditField.setText(""); if (!wordListModel.isEmpty()) { wordList.setSelectedIndex(Math.min(index, wordListModel.size() - 1)); } }); fieldList.addListSelectionListener(e -> { currentField = fieldList.getSelectedValue(); fieldNameField.setText(""); setupWordSelector(); }); newField.addActionListener(e -> { if (!fieldListModel.get(0).equals(FIELD_FIRST_LINE)) { // only add <field name> once fieldListModel.add(0, FIELD_FIRST_LINE); } fieldList.setSelectedIndex(0); fPane.getVerticalScrollBar().setValue(0); fieldNameField.setEnabled(true); fieldNameField.setText(currentField); fieldNameField.selectAll(); fieldNameField.requestFocus(); }); fieldNameField.addActionListener(e -> fieldNameField.transferFocus()); fieldNameField.addFocusListener(new FieldNameFocusAdapter()); removeField.addActionListener(e -> { int index = fieldList.getSelectedIndex(); if (index == -1) { return; } String fieldName = fieldListModel.get(index); removedFields.add(fieldName); fieldListModel.remove(index); wordListModels.remove(fieldName); fieldNameField.setText(""); if (!fieldListModel.isEmpty()) { fieldList.setSelectedIndex(Math.min(index, wordListModel.size() - 1)); } }); ok.addActionListener(e -> { try { applyChanges(); dispose(); } catch (Exception ex) { LOGGER.info("Could not apply changes in \"Manage content selectors\"", ex); JOptionPane.showMessageDialog(frame, Localization.lang("Could not apply changes.")); } }); apply.addActionListener(e -> { // Store if an entry is currently being edited: if (!"".equals(wordEditField.getText())) { wordEditFieldListener.actionPerformed(null); } try { applyChanges(); } catch (Exception ex) { LOGGER.info("Could not apply changes in \"Manage content selectors\"", ex); JOptionPane.showMessageDialog(frame, Localization.lang("Could not apply changes.")); } }); Action cancelAction = new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { dispose(); } }; cancelAction.putValue(Action.NAME, Localization.lang("Cancel")); cancel.setAction(cancelAction); } private void actOnWordEdit() { String old = wordList.getSelectedValue(); String newVal = wordEditField.getText(); if ("".equals(newVal) || newVal.equals(old)) { return; // Empty string or no change. } int index = wordList.getSelectedIndex(); if (wordListModel.contains(newVal)) { // ensure that word already in list is visible index = wordListModel.indexOf(newVal); wordList.ensureIndexIsVisible(index); return; } int newIndex = findPos(wordListModel, newVal); if (index >= 0) { // initiate replacement of selected word wordListModel.remove(index); if (newIndex > index) { // newIndex has to be adjusted after removal of previous entry newIndex--; } } wordListModel.add(newIndex, newVal); wordList.ensureIndexIsVisible(newIndex); wordEditField.selectAll(); } private void newWordAction() { if (wordListModel.isEmpty() || !wordListModel.get(0).equals(WORD_FIRSTLINE_TEXT)) { wordListModel.add(0, WORD_FIRSTLINE_TEXT); } wordList.setSelectedIndex(0); wPane.getVerticalScrollBar().setValue(0); } private void applyChanges() { boolean changedFieldSet = false; // Watch if we need to rebuild entry editors boolean anythingChanged = false; // Watch if we should mark as there is data changed // First remove the mappings for fields that have been deleted. // If these were re-added, they will be added below, so it doesn't // cause any harm to remove them here. for (String fieldName : removedFields) { metaData.clearContentSelectors(fieldName); changedFieldSet = true; anythingChanged = true; } // Cycle through all fields that we have created listmodels for: for (Map.Entry<String, DefaultListModel<String>> entry : wordListModels.entrySet()) { // For each field name, store the values: if ((entry.getKey() == null) || FIELD_FIRST_LINE.equals(entry.getKey())) { continue; } DefaultListModel<String> lm = entry.getValue(); int start = 0; // Avoid storing the <new word> marker if it is there: if (!lm.isEmpty()) { while ((start < lm.size()) && lm.get(start).equals(WORD_FIRSTLINE_TEXT)) { start++; } } Set<String> data = new HashSet<>(); for (int wrd = start; wrd < lm.size(); wrd++) { String word = lm.get(wrd); data.add(word); } // Check if any words have been added if (!data.equals(new HashSet<>(metaData.getContentSelectorValuesForField(entry.getKey())))) { anythingChanged = true; } // Check if there are words to be added and previously there were no content selector for the field if (!data.isEmpty() && metaData.getContentSelectorValuesForField(entry.getKey()).isEmpty()) { changedFieldSet = true; } metaData.addContentSelector(new ContentSelector(entry.getKey(), new ArrayList<>(data))); } // Update all selectors in the current BasePanel. if (changedFieldSet) { // We have added or removed content selectors, update the entry editor panel.rebuildAllEntryEditors(); } else if (anythingChanged) { // Enough to update the content selectors, if anything changed panel.updateAllContentSelectors(); } if (anythingChanged) { // Mark the database updated so changes are not lost panel.markNonUndoableBaseChanged(); } panel.getAutoCompleters().addContentSelectorValuesToAutoCompleters(panel.getBibDatabaseContext().getMetaData()); } /** * Set the contents of the field selector list. * */ private void setupFieldSelector() { fieldListModel.clear(); SortedSet<String> contents = new TreeSet<>(); ContentSelectors selectors = metaData.getContentSelectors(); for (String s : selectors.getFieldNamesWithSelectors()) { contents.add(s); } if (contents.isEmpty()) { // if nothing was added, put the default fields (as described in the help) fieldListModel.addElement(FieldName.AUTHOR); fieldListModel.addElement(FieldName.JOURNAL); fieldListModel.addElement(FieldName.KEYWORDS); fieldListModel.addElement(FieldName.PUBLISHER); } else { for (String s : contents) { fieldListModel.addElement(s); } } if (currentField == null) { // if dialog is created for the whole database, // select the first field to avoid confusions in GUI usage fieldList.setSelectedIndex(0); } else { // a specific field has been chosen at the constructor // select this field int i = fieldListModel.indexOf(currentField); if (i != -1) { // field has been found in list, select it fieldList.setSelectedIndex(i); } } } private void setupWordSelector() { // Have we already created a listmodel for this field? wordListModel = wordListModels.get(currentField); if (wordListModel == null) { wordListModel = new DefaultListModel<>(); wordList.setModel(wordListModel); wordListModels.put(currentField, wordListModel); int index = 0; for (String s : metaData.getContentSelectorValuesForField(currentField)) { wordListModel.add(index, s); index++; } } else { wordList.setModel(wordListModel); } } private static int findPos(DefaultListModel<String> lm, String item) { for (int i = 0; i < lm.size(); i++) { String s = lm.get(i); if (item.compareToIgnoreCase(s) < 0) { // item precedes s return i; } } return lm.size(); } private void initLayout() { fieldNameField.setEnabled(false); fieldList.setVisibleRowCount(4); wordList.setVisibleRowCount(10); final String VAL = "Uren luren himmelturen, ja Besseggen."; fieldList.setPrototypeCellValue(VAL); wordList.setPrototypeCellValue(VAL); fieldPan.setBorder( BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), Localization.lang("Field name"))); wordPan.setBorder( BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), Localization.lang("Keyword"))); fieldPan.setLayout(gbl); wordPan.setLayout(gbl); con.insets = new Insets(2, 2, 2, 2); con.fill = GridBagConstraints.BOTH; con.gridwidth = 2; con.weightx = 1; con.weighty = 1; con.gridx = 0; con.gridy = 0; gbl.setConstraints(fPane, con); fieldPan.add(fPane); gbl.setConstraints(wPane, con); wordPan.add(wPane); con.gridwidth = 1; con.gridx = 2; //con.weightx = 0.7; con.gridheight = 2; gbl.setConstraints(fieldNamePan, con); fieldPan.add(fieldNamePan); gbl.setConstraints(wordEditPan, con); wordPan.add(wordEditPan); con.gridx = 0; con.gridy = 1; con.weightx = 0; con.weighty = 0; con.gridwidth = 1; con.gridheight = 1; con.fill = GridBagConstraints.NONE; con.anchor = GridBagConstraints.WEST; gbl.setConstraints(newField, con); fieldPan.add(newField); gbl.setConstraints(newWord, con); wordPan.add(newWord); con.gridx = 1; //con.anchor = GridBagConstraints.EAST; gbl.setConstraints(removeField, con); fieldPan.add(removeField); gbl.setConstraints(removeWord, con); wordPan.add(removeWord); con.anchor = GridBagConstraints.WEST; con.gridx = 0; con.gridy = 0; gbl.setConstraints(fieldNameField, con); fieldNamePan.add(fieldNameField); gbl.setConstraints(wordEditField, con); wordEditPan.add(wordEditField); // Add buttons: ButtonBarBuilder bsb = new ButtonBarBuilder(buttonPan); bsb.addGlue(); bsb.addButton(ok); bsb.addButton(apply); bsb.addButton(cancel); bsb.addRelatedGap(); bsb.addButton(new HelpAction(HelpFile.CONTENT_SELECTOR).getHelpButton()); bsb.addGlue(); // Add panels to dialog: con.fill = GridBagConstraints.BOTH; getContentPane().setLayout(gbl); con.weightx = 1; con.weighty = 0.5; con.gridwidth = 1; con.gridheight = 1; con.gridx = 0; con.gridy = 0; gbl.setConstraints(fieldPan, con); getContentPane().add(fieldPan); con.gridy = 1; gbl.setConstraints(wordPan, con); getContentPane().add(wordPan); con.weighty = 0; con.gridy = 2; con.insets = new Insets(12, 2, 2, 2); gbl.setConstraints(buttonPan, con); getContentPane().add(buttonPan); } private class FieldNameFocusAdapter extends FocusAdapter { /** * Adds the text value to the list */ @Override public void focusLost(FocusEvent e) { String s = fieldNameField.getText(); fieldNameField.setText(""); fieldNameField.setEnabled(false); if (!FIELD_FIRST_LINE.equals(s) && !"".equals(s)) { // user has typed something // remove "<first name>" from list fieldListModel.remove(0); int pos; if (fieldListModel.contains(s)) { // field already exists, scroll to that field (below) pos = fieldListModel.indexOf(s); } else { // Add new field. pos = findPos(fieldListModel, s); fieldListModel.add(Math.max(0, pos), s); } fieldList.setSelectedIndex(pos); fieldList.ensureIndexIsVisible(pos); currentField = s; setupWordSelector(); newWordAction(); } } } }