package org.jabref.gui.contentselector;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Window;
import java.awt.event.ActionEvent;
import javax.swing.AbstractAction;
import javax.swing.Box;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.KeyStroke;
import org.jabref.gui.BasePanel;
import org.jabref.gui.JabRefFrame;
import org.jabref.gui.fieldeditors.FieldEditor;
import org.jabref.logic.l10n.Localization;
import org.jabref.model.entry.Keyword;
import org.jabref.model.entry.KeywordList;
import org.jabref.model.metadata.MetaData;
import com.jgoodies.forms.layout.Sizes;
import com.jgoodies.looks.Options;
/**
* A combo-box and a manage button that will add selected strings to an
* associated entry editor.
*
* Used to manage keywords and authors for instance.
*/
public class FieldContentSelector extends JComponent {
private static final int MAX_CONTENT_SELECTOR_WIDTH = 240; // The max width of the combobox for content selectors.
private final JComboBox<String> comboBox;
private final FieldEditor editor;
private final MetaData metaData;
private final AbstractAction action;
private final String delimiter;
/**
*
* Create a new FieldContentSelector.
*
* @param frame
* The one JabRef-Frame.
* @param panel
* The basepanel the entry-editor is on.
* @param owner
* The window/frame/dialog which should be the owner of the
* content selector dialog.
* @param editor
* The entry editor which will be appended by the text selected
* by the user from the combobox.
* @param action
* The action that will be performed to after an item from the
* combobox has been appended to the text in the entryeditor.
* @param horizontalLayout
* Whether to put a 2 pixel horizontal strut between combobox and
* button.
*/
public FieldContentSelector(JabRefFrame frame, final BasePanel panel, Window owner, final FieldEditor editor,
final AbstractAction action, boolean horizontalLayout, String delimiter) {
this.editor = editor;
this.metaData = panel.getBibDatabaseContext().getMetaData();
this.action = action;
this.delimiter = delimiter;
comboBox = new JComboBox<String>() {
@Override
public Dimension getPreferredSize() {
Dimension parents = super.getPreferredSize();
if (parents.width > MAX_CONTENT_SELECTOR_WIDTH) {
parents.width = MAX_CONTENT_SELECTOR_WIDTH;
}
return parents;
}
};
GridBagLayout gbl = new GridBagLayout();
GridBagConstraints con = new GridBagConstraints();
setLayout(gbl);
// comboBox.setEditable(true);
comboBox.setMaximumRowCount(35);
// Set the width of the popup independent of the size of th box itself:
comboBox.putClientProperty(Options.COMBO_POPUP_PROTOTYPE_DISPLAY_VALUE_KEY,
"The longest text in the combo popup menu. And even longer.");
rebuildComboBox();
con.gridwidth = horizontalLayout ? 3 : GridBagConstraints.REMAINDER;
con.fill = GridBagConstraints.HORIZONTAL;
con.weightx = 1;
gbl.setConstraints(comboBox, con);
comboBox.addActionListener(e -> {
/*
* These conditions signify arrow key navigation in the dropdown
* list, so we should not react to it. I'm not sure if this is
* well defined enough to be guaranteed to work everywhere.
*/
if ("comboBoxChanged".equals(e.getActionCommand()) && (e.getModifiers() == 0)) {
return;
}
selectionMade();
});
// Add an action for the Enter key that signals a selection:
comboBox.getInputMap().put(KeyStroke.getKeyStroke("ENTER"), "enter");
comboBox.getActionMap().put("enter", new AbstractAction() {
@Override
public void actionPerformed(ActionEvent actionEvent) {
selectionMade();
comboBox.setPopupVisible(false);
}
});
add(comboBox);
if (horizontalLayout) {
add(Box.createHorizontalStrut(Sizes.dialogUnitXAsPixel(2, this)));
}
JButton manage = new JButton(Localization.lang("Manage"));
gbl.setConstraints(manage, con);
add(manage);
manage.addActionListener(e -> {
ContentSelectorDialog csd = new ContentSelectorDialog(owner, frame, panel, true, editor.getFieldName());
csd.setLocationRelativeTo(frame);
// Calling setVisible(true) will open the modal dialog and block
// for the dialog to close.
csd.setVisible(true);
// So we need to rebuild the ComboBox afterwards
rebuildComboBox();
});
}
private void selectionMade() {
// The first element is empty to avoid a preselection
if (comboBox.getSelectedIndex() == 0) {
return;
}
String chosen = (String) comboBox.getSelectedItem();
if ((chosen == null) || chosen.isEmpty()) {
return;
}
String currentText = editor.getText();
KeywordList words = KeywordList.parse(currentText, this.delimiter.charAt(0));
boolean alreadyInList = words.contains(new Keyword(chosen));
// not the first word and no duplicate -> we need a comma
if (!"".equals(currentText) && !alreadyInList) {
editor.append(FieldContentSelector.this.delimiter);
}
// no duplicate -> add it
if (!alreadyInList) {
editor.append(chosen);
}
comboBox.setSelectedIndex(0);
// Fire event that we changed the editor
if (action != null) {
action.actionPerformed(new ActionEvent(editor, 0, ""));
}
// Transfer focus to the editor.
editor.requestFocus();
}
public void rebuildComboBox() {
comboBox.removeAllItems();
// To have an empty field as the default for the combobox
comboBox.addItem("");
for (String item : metaData.getContentSelectorValuesForField(editor.getFieldName())) {
comboBox.addItem(item);
}
}
}