Java tutorial
/* Copyright (C) 2003-2011 JabRef contributors. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ package net.sf.jabref.groups; import java.awt.BorderLayout; import java.awt.CardLayout; import java.awt.Container; import java.awt.Dimension; import java.awt.Font; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.util.Vector; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; import javax.swing.*; import javax.swing.event.CaretEvent; import javax.swing.event.CaretListener; import javax.swing.undo.AbstractUndoableEdit; import com.jgoodies.forms.factories.Borders; import net.sf.jabref.*; import net.sf.jabref.groups.structure.*; import net.sf.jabref.search.describer.BasicSearchDescriber; import net.sf.jabref.search.describer.SearchExpressionDescriber; import net.sf.jabref.search.rules.SearchExpression; import net.sf.jabref.util.StringUtil; import net.sf.jabref.util.Util; import com.jgoodies.forms.builder.ButtonBarBuilder; import com.jgoodies.forms.builder.DefaultFormBuilder; import com.jgoodies.forms.layout.FormLayout; /** * Dialog for creating or modifying groups. Operates directly on the Vector * containing group information. */ class GroupDialog extends JDialog { private static final int INDEX_EXPLICITGROUP = 0; private static final int INDEX_KEYWORDGROUP = 1; private static final int INDEX_SEARCHGROUP = 2; private static final int TEXTFIELD_LENGTH = 30; // for all types private final JTextField m_name = new JTextField(GroupDialog.TEXTFIELD_LENGTH); private final JRadioButton m_explicitRadioButton = new JRadioButton( Globals.lang("Statically group entries by manual assignment")); private final JRadioButton m_keywordsRadioButton = new JRadioButton( Globals.lang("Dynamically group entries by searching a field for a keyword")); private final JRadioButton m_searchRadioButton = new JRadioButton( Globals.lang("Dynamically group entries by a free-form search expression")); private final JRadioButton m_independentButton = new JRadioButton( // JZTODO lyrics Globals.lang("Independent group: When selected, view only this group's entries")); private final JRadioButton m_intersectionButton = new JRadioButton( // JZTODO lyrics Globals.lang( "Refine supergroup: When selected, view entries contained in both this group and its supergroup")); private final JRadioButton m_unionButton = new JRadioButton( // JZTODO lyrics Globals.lang( "Include subgroups: When selected, view entries contained in this group or its subgroups")); // for KeywordGroup private final JTextField m_kgSearchField = new JTextField(GroupDialog.TEXTFIELD_LENGTH); private final FieldTextField m_kgSearchTerm = new FieldTextField("keywords", "", false); private final JCheckBox m_kgCaseSensitive = new JCheckBox(Globals.lang("Case sensitive")); private final JCheckBox m_kgRegExp = new JCheckBox(Globals.lang("Regular Expression")); // for SearchGroup private final JTextField m_sgSearchExpression = new JTextField(GroupDialog.TEXTFIELD_LENGTH); private final JCheckBox m_sgCaseSensitive = new JCheckBox(Globals.lang("Case sensitive")); private final JCheckBox m_sgRegExp = new JCheckBox(Globals.lang("Regular Expression")); // for all types private final JButton m_ok = new JButton(Globals.lang("Ok")); private final JPanel m_optionsPanel = new JPanel(); private final JLabel m_description = new JLabel() { @Override public Dimension getPreferredSize() { Dimension d = super.getPreferredSize(); // width must be smaller than width of enclosing JScrollPane // to prevent a horizontal scroll bar d.width = 1; return d; } }; private boolean m_okPressed = false; private final BasePanel m_basePanel; private AbstractGroup m_resultingGroup; private AbstractUndoableEdit m_undoAddPreviousEntires = null; private final AbstractGroup m_editedGroup; private final CardLayout m_optionsLayout = new CardLayout(); /** * Shows a group add/edit dialog. * * @param jabrefFrame * The parent frame. * @param basePanel * The default grouping field. * @param editedGroup * The group being edited, or null if a new group is to be * created. */ public GroupDialog(JabRefFrame jabrefFrame, BasePanel basePanel, AbstractGroup editedGroup) { super(jabrefFrame, Globals.lang("Edit group"), true); m_basePanel = basePanel; m_editedGroup = editedGroup; // set default values (overwritten if editedGroup != null) m_kgSearchField.setText(jabrefFrame.prefs().get(JabRefPreferences.GROUPS_DEFAULT_FIELD)); // configure elements ButtonGroup groupType = new ButtonGroup(); groupType.add(m_explicitRadioButton); groupType.add(m_keywordsRadioButton); groupType.add(m_searchRadioButton); ButtonGroup groupHierarchy = new ButtonGroup(); groupHierarchy.add(m_independentButton); groupHierarchy.add(m_intersectionButton); groupHierarchy.add(m_unionButton); m_description.setVerticalAlignment(SwingConstants.TOP); getRootPane().setDefaultButton(m_ok); // build individual layout cards for each group m_optionsPanel.setLayout(m_optionsLayout); // ... for explicit group m_optionsPanel.add(new JPanel(), "" + GroupDialog.INDEX_EXPLICITGROUP); // ... for keyword group FormLayout layoutKG = new FormLayout("right:pref, 4dlu, fill:1dlu:grow, 2dlu, left:pref"); DefaultFormBuilder builderKG = new DefaultFormBuilder(layoutKG); builderKG.append(Globals.lang("Field")); builderKG.append(m_kgSearchField, 3); builderKG.nextLine(); builderKG.append(Globals.lang("Keyword")); builderKG.append(m_kgSearchTerm); builderKG.append(new FieldContentSelector(jabrefFrame, m_basePanel, this, m_kgSearchTerm, m_basePanel.metaData(), null, true, ", ")); builderKG.nextLine(); builderKG.append(m_kgCaseSensitive, 3); builderKG.nextLine(); builderKG.append(m_kgRegExp, 3); m_optionsPanel.add(builderKG.getPanel(), "" + GroupDialog.INDEX_KEYWORDGROUP); // ... for search group FormLayout layoutSG = new FormLayout("right:pref, 4dlu, fill:1dlu:grow"); DefaultFormBuilder builderSG = new DefaultFormBuilder(layoutSG); builderSG.append(Globals.lang("Search expression")); builderSG.append(m_sgSearchExpression); builderSG.nextLine(); builderSG.append(m_sgCaseSensitive, 3); builderSG.nextLine(); builderSG.append(m_sgRegExp, 3); m_optionsPanel.add(builderSG.getPanel(), "" + GroupDialog.INDEX_SEARCHGROUP); // ... for buttons panel FormLayout layoutBP = new FormLayout("pref, 4dlu, pref", "p"); layoutBP.setColumnGroups(new int[][] { { 1, 3 } }); ButtonBarBuilder builderBP = new ButtonBarBuilder(); builderBP.addGlue(); builderBP.addButton(m_ok); JButton m_cancel = new JButton(Globals.lang("Cancel")); builderBP.addButton(m_cancel); builderBP.addGlue(); builderBP.getPanel().setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); // create layout FormLayout layoutAll = new FormLayout("right:pref, 4dlu, fill:600px, 4dlu, fill:pref", "p, 3dlu, p, 3dlu, p, 0dlu, p, 0dlu, p, 3dlu, p, 3dlu, p, " + "0dlu, p, 0dlu, p, 3dlu, p, 3dlu, " + "p, 3dlu, p, 3dlu, top:80dlu, 9dlu, p, 9dlu, p"); DefaultFormBuilder builderAll = new DefaultFormBuilder(layoutAll); builderAll.border(Borders.DIALOG); builderAll.appendSeparator(Globals.lang("General")); builderAll.nextLine(); builderAll.nextLine(); builderAll.append(Globals.lang("Name")); builderAll.append(m_name); builderAll.nextLine(); builderAll.nextLine(); builderAll.append(m_explicitRadioButton, 5); builderAll.nextLine(); builderAll.nextLine(); builderAll.append(m_keywordsRadioButton, 5); builderAll.nextLine(); builderAll.nextLine(); builderAll.append(m_searchRadioButton, 5); builderAll.nextLine(); builderAll.nextLine(); builderAll.appendSeparator(Globals.lang("Hierarchical context")); // JZTODO lyrics builderAll.nextLine(); builderAll.nextLine(); builderAll.append(m_independentButton, 5); builderAll.nextLine(); builderAll.nextLine(); builderAll.append(m_intersectionButton, 5); builderAll.nextLine(); builderAll.nextLine(); builderAll.append(m_unionButton, 5); builderAll.nextLine(); builderAll.nextLine(); builderAll.appendSeparator(Globals.lang("Options")); builderAll.nextLine(); builderAll.nextLine(); builderAll.append(m_optionsPanel, 5); builderAll.nextLine(); builderAll.nextLine(); builderAll.appendSeparator(Globals.lang("Description")); builderAll.nextLine(); builderAll.nextLine(); JScrollPane sp = new JScrollPane(m_description, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED) { @Override public Dimension getPreferredSize() { return getMaximumSize(); } }; builderAll.append(sp, 5); builderAll.nextLine(); builderAll.nextLine(); builderAll.appendSeparator(); builderAll.nextLine(); builderAll.nextLine(); //CellConstraints cc = new CellConstraints(); //builderAll.add(builderBP.getPanel(), cc.xyw(builderAll.getColumn(), // builderAll.getRow(), 5, "center, fill")); Container cp = getContentPane(); //cp.setLayout(new BoxLayout(cp, BoxLayout.Y_AXIS)); cp.add(builderAll.getPanel(), BorderLayout.CENTER); cp.add(builderBP.getPanel(), BorderLayout.SOUTH); pack(); setResizable(false); updateComponents(); setLayoutForSelectedGroup(); Util.placeDialog(this, jabrefFrame); // add listeners ItemListener radioButtonItemListener = new ItemListener() { @Override public void itemStateChanged(ItemEvent e) { setLayoutForSelectedGroup(); updateComponents(); } }; m_explicitRadioButton.addItemListener(radioButtonItemListener); m_keywordsRadioButton.addItemListener(radioButtonItemListener); m_searchRadioButton.addItemListener(radioButtonItemListener); Action cancelAction = new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { dispose(); } }; m_cancel.addActionListener(cancelAction); builderAll.getPanel().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW) .put(Globals.prefs.getKey("Close dialog"), "close"); builderAll.getPanel().getActionMap().put("close", cancelAction); m_ok.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { m_okPressed = true; if (m_explicitRadioButton.isSelected()) { if (m_editedGroup instanceof ExplicitGroup) { // keep assignments from possible previous ExplicitGroup m_resultingGroup = m_editedGroup.deepCopy(); m_resultingGroup.setName(m_name.getText().trim()); m_resultingGroup.setHierarchicalContext(getContext()); } else { m_resultingGroup = new ExplicitGroup(m_name.getText().trim(), getContext()); if (m_editedGroup != null) { addPreviousEntries(); } } } else if (m_keywordsRadioButton.isSelected()) { // regex is correct, otherwise OK would have been disabled // therefore I don't catch anything here m_resultingGroup = new KeywordGroup(m_name.getText().trim(), m_kgSearchField.getText().trim(), m_kgSearchTerm.getText().trim(), m_kgCaseSensitive.isSelected(), m_kgRegExp.isSelected(), getContext()); if (((m_editedGroup instanceof ExplicitGroup) || (m_editedGroup instanceof SearchGroup)) && m_resultingGroup.supportsAdd()) { addPreviousEntries(); } } else if (m_searchRadioButton.isSelected()) { try { // regex is correct, otherwise OK would have been // disabled // therefore I don't catch anything here m_resultingGroup = new SearchGroup(m_name.getText().trim(), m_sgSearchExpression.getText().trim(), isCaseSensitive(), isRegex(), getContext()); } catch (Exception e1) { // should never happen } } dispose(); } }); CaretListener caretListener = new CaretListener() { @Override public void caretUpdate(CaretEvent e) { updateComponents(); } }; ItemListener itemListener = new ItemListener() { @Override public void itemStateChanged(ItemEvent e) { updateComponents(); } }; m_name.addCaretListener(caretListener); m_kgSearchField.addCaretListener(caretListener); m_kgSearchTerm.addCaretListener(caretListener); m_kgCaseSensitive.addItemListener(itemListener); m_kgRegExp.addItemListener(itemListener); m_sgSearchExpression.addCaretListener(caretListener); m_sgRegExp.addItemListener(itemListener); m_sgCaseSensitive.addItemListener(itemListener); // configure for current type if (editedGroup instanceof KeywordGroup) { KeywordGroup group = (KeywordGroup) editedGroup; m_name.setText(group.getName()); m_kgSearchField.setText(group.getSearchField()); m_kgSearchTerm.setText(group.getSearchExpression()); m_kgCaseSensitive.setSelected(group.isCaseSensitive()); m_kgRegExp.setSelected(group.isRegExp()); m_keywordsRadioButton.setSelected(true); setContext(editedGroup.getHierarchicalContext()); } else if (editedGroup instanceof SearchGroup) { SearchGroup group = (SearchGroup) editedGroup; m_name.setText(group.getName()); m_sgSearchExpression.setText(group.getSearchExpression()); m_sgCaseSensitive.setSelected(group.isCaseSensitive()); m_sgRegExp.setSelected(group.isRegExp()); m_searchRadioButton.setSelected(true); setContext(editedGroup.getHierarchicalContext()); } else if (editedGroup instanceof ExplicitGroup) { m_name.setText(editedGroup.getName()); m_explicitRadioButton.setSelected(true); setContext(editedGroup.getHierarchicalContext()); } else { // creating new group -> defaults! m_explicitRadioButton.setSelected(true); setContext(GroupHierarchyType.INDEPENDENT); } } public boolean okPressed() { return m_okPressed; } public AbstractGroup getResultingGroup() { return m_resultingGroup; } private void setLayoutForSelectedGroup() { if (m_explicitRadioButton.isSelected()) { m_optionsLayout.show(m_optionsPanel, String.valueOf(GroupDialog.INDEX_EXPLICITGROUP)); } else if (m_keywordsRadioButton.isSelected()) { m_optionsLayout.show(m_optionsPanel, String.valueOf(GroupDialog.INDEX_KEYWORDGROUP)); } else if (m_searchRadioButton.isSelected()) { m_optionsLayout.show(m_optionsPanel, String.valueOf(GroupDialog.INDEX_SEARCHGROUP)); } } private void updateComponents() { // all groups need a name boolean okEnabled = !m_name.getText().trim().isEmpty(); if (!okEnabled) { setDescription(Globals.lang("Please enter a name for the group.")); m_ok.setEnabled(false); return; } String s1, s2; if (m_keywordsRadioButton.isSelected()) { s1 = m_kgSearchField.getText().trim(); okEnabled = okEnabled && s1.matches("\\w+"); s2 = m_kgSearchTerm.getText().trim(); okEnabled = okEnabled && (!s2.isEmpty()); if (!okEnabled) { setDescription(Globals.lang( "Please enter the field to search (e.g. <b>keywords</b>) and the keyword to search it for (e.g. <b>electrical</b>).")); } else { if (m_kgRegExp.isSelected()) { try { Pattern.compile(s2); setDescription(KeywordGroup.getDescriptionForPreview(s1, s2, m_kgCaseSensitive.isSelected(), m_kgRegExp.isSelected())); } catch (Exception e) { okEnabled = false; setDescription(formatRegExException(s2, e)); } } else { setDescription(KeywordGroup.getDescriptionForPreview(s1, s2, m_kgCaseSensitive.isSelected(), m_kgRegExp.isSelected())); } } setNameFontItalic(true); } else if (m_searchRadioButton.isSelected()) { s1 = m_sgSearchExpression.getText().trim(); okEnabled = okEnabled & (!s1.isEmpty()); if (!okEnabled) { setDescription(Globals.lang( "Please enter a search term. For example, to search all fields for <b>Smith</b>, enter%c<p>" + "<tt>smith</tt><p>" + "To search the field <b>Author</b> for <b>Smith</b> and the field <b>Title</b> for <b>electrical</b>, enter%c<p>" + "<tt>author%esmith and title%eelectrical</tt>")); } else { SearchExpression expression = new SearchExpression(isCaseSensitive(), isRegex()); expression.validateSearchStrings(s1); if (expression.getTree() != null) { setDescription(new SearchExpressionDescriber(isCaseSensitive(), isRegex(), expression.getTree()) .getDescription()); } else { setDescription(new BasicSearchDescriber(isCaseSensitive(), isRegex(), s1).getDescription()); } if (isRegex()) { try { Pattern.compile(s1); } catch (Exception e) { okEnabled = false; setDescription(formatRegExException(s1, e)); } } } setNameFontItalic(true); } else if (m_explicitRadioButton.isSelected()) { setDescription(ExplicitGroup.getDescriptionForPreview()); setNameFontItalic(false); } m_ok.setEnabled(okEnabled); } private boolean isRegex() { return m_sgRegExp.isSelected(); } private boolean isCaseSensitive() { return m_sgCaseSensitive.isSelected(); } /** * This is used when a group is converted and the new group supports * explicit adding of entries: All entries that match the previous group are * added to the new group. */ private void addPreviousEntries() { // JZTODO lyrics... int i = JOptionPane.showConfirmDialog(m_basePanel.frame(), Globals.lang("Assign the original group's entries to this group?"), Globals.lang("Change of Grouping Method"), JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); if (i == JOptionPane.NO_OPTION) { return; } Vector<BibtexEntry> vec = new Vector<BibtexEntry>(); for (BibtexEntry entry : m_basePanel.database().getEntries()) { if (m_editedGroup.contains(entry)) { vec.add(entry); } } if (!vec.isEmpty()) { BibtexEntry[] entries = new BibtexEntry[vec.size()]; vec.toArray(entries); if (!Util.warnAssignmentSideEffects(new AbstractGroup[] { m_resultingGroup }, entries, m_basePanel.getDatabase(), this)) { return; } // the undo information for a conversion to an ExplicitGroup is // contained completely in the UndoableModifyGroup object. if (!(m_resultingGroup instanceof ExplicitGroup)) { m_undoAddPreviousEntires = m_resultingGroup.add(entries); } } } private void setDescription(String description) { m_description.setText("<html>" + description + "</html>"); } private String formatRegExException(String regExp, Exception e) { String[] sa = e.getMessage().split("\\n"); StringBuilder sb = new StringBuilder(); for (int i = 0; i < sa.length; ++i) { if (i > 0) { sb.append("<br>"); } sb.append(StringUtil.quoteForHTML(sa[i])); } String s = Globals.lang("The regular expression <b>%0</b> is invalid%c", StringUtil.quoteForHTML(regExp)) + "<p><tt>" + sb.toString() + "</tt>"; if (!(e instanceof PatternSyntaxException)) { return s; } int lastNewline = s.lastIndexOf("<br>"); int hat = s.lastIndexOf("^"); if ((lastNewline >= 0) && (hat >= 0) && (hat > lastNewline)) { return s.substring(0, lastNewline + 4) + s.substring(lastNewline + 4).replaceAll(" ", " "); } return s; } /** * Returns an undo object for adding the edited group's entries to the new * group, or null if this did not occur. */ public AbstractUndoableEdit getUndoForAddPreviousEntries() { return m_undoAddPreviousEntires; } /** Sets the font of the name entry field. */ private void setNameFontItalic(boolean italic) { Font f = m_name.getFont(); if (f.isItalic() != italic) { f = f.deriveFont(italic ? Font.ITALIC : Font.PLAIN); m_name.setFont(f); } } /** * Returns the int representing the selected hierarchical group context. */ private GroupHierarchyType getContext() { if (m_independentButton.isSelected()) { return GroupHierarchyType.INDEPENDENT; } if (m_intersectionButton.isSelected()) { return GroupHierarchyType.REFINING; } if (m_unionButton.isSelected()) { return GroupHierarchyType.INCLUDING; } return GroupHierarchyType.INDEPENDENT; // default } private void setContext(GroupHierarchyType context) { if (context == GroupHierarchyType.REFINING) { m_intersectionButton.setSelected(true); } else if (context == GroupHierarchyType.INCLUDING) { m_unionButton.setSelected(true); } else { m_independentButton.setSelected(true); } } }