/* 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.oo; import ca.odell.glazedlists.BasicEventList; import ca.odell.glazedlists.EventList; import ca.odell.glazedlists.SortedList; import ca.odell.glazedlists.event.ListEvent; import ca.odell.glazedlists.event.ListEventListener; import ca.odell.glazedlists.gui.TableFormat; import ca.odell.glazedlists.swing.EventSelectionModel; import ca.odell.glazedlists.swing.EventTableModel; import com.jgoodies.forms.builder.ButtonBarBuilder; import com.jgoodies.forms.builder.DefaultFormBuilder; import com.jgoodies.forms.layout.FormLayout; import com.jgoodies.uif_lite.component.UIFSplitPane; import net.sf.jabref.*; import net.sf.jabref.external.ExternalFileType; import net.sf.jabref.external.UnknownExternalFileType; import javax.swing.*; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import javax.swing.table.TableColumnModel; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.io.*; import java.net.URL; import java.util.Iterator; import java.util.Set; /** * This class produces a dialog box for choosing a style file. */ public class StyleSelectDialog { public static final String STYLE_FILE_EXTENSION = ".jstyle"; private JabRefFrame frame; private EventList<OOBibStyle> styles, sortedStyles; private JDialog diag; private JTable table; private UIFSplitPane contentPane = new UIFSplitPane(UIFSplitPane.VERTICAL_SPLIT); private EventTableModel tableModel; private EventSelectionModel<OOBibStyle> selectionModel; private JPopupMenu popup = new JPopupMenu(); private JMenuItem edit = new JMenuItem(Globals.lang("Edit")); private JRadioButton useDefaultAuthoryear = new JRadioButton(Globals.lang("Default style (author-year citations)")), useDefaultNumerical = new JRadioButton(Globals.lang("Default style (numerical citations)")), chooseDirectly = new JRadioButton(Globals.lang("Choose style file directly")+":"), setDirectory = new JRadioButton(Globals.lang("Choose from a directory")+":"); private JTextField directFile = new JTextField(), styleDir = new JTextField(); private JButton browseDirectFile = new JButton(Globals.lang("Browse")), browseStyleDir = new JButton(Globals.lang("Browse")), showDefaultAuthoryearStyle = new JButton(Globals.lang("View")), showDefaultNumericalStyle = new JButton(Globals.lang("View")); PreviewPanel preview; private Rectangle toRect = new Rectangle(0, 0, 1, 1); private JButton ok = new JButton(Globals.lang("Ok")), cancel = new JButton(Globals.lang("Cancel")); private BibtexEntry prevEntry = new BibtexEntry(Util.createNeutralId()); private boolean okPressed = false; private String initSelection; public StyleSelectDialog(JabRefFrame frame, String initSelection) { this.frame = frame; setupPrevEntry(); init(initSelection); } private void init(String initSelection) { this.initSelection = initSelection; ButtonGroup bg = new ButtonGroup(); bg.add(useDefaultAuthoryear); bg.add(useDefaultNumerical); bg.add(chooseDirectly); bg.add(setDirectory); if (Globals.prefs.getBoolean("ooUseDefaultAuthoryearStyle")) useDefaultAuthoryear.setSelected(true); else if (Globals.prefs.getBoolean("ooUseDefaultNumericalStyle")) useDefaultNumerical.setSelected(true); else { if (Globals.prefs.getBoolean("ooChooseStyleDirectly")) chooseDirectly.setSelected(true); else setDirectory.setSelected(true); } directFile.setText(Globals.prefs.get("ooDirectFile")); styleDir.setText(Globals.prefs.get("ooStyleDirectory")); directFile.setEditable(false); styleDir.setEditable(false); popup.add(edit); BrowseAction dfBrowse = new BrowseAction(null, directFile, false); dfBrowse.setFocusTarget(directFile); browseDirectFile.addActionListener(dfBrowse); BrowseAction sdBrowse = new BrowseAction(null, styleDir, true); sdBrowse.setFocusTarget(setDirectory); browseStyleDir.addActionListener(sdBrowse); showDefaultAuthoryearStyle.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent actionEvent) { displayDefaultStyle(true); } }); showDefaultNumericalStyle.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent actionEvent) { displayDefaultStyle(false); } }); // Add action listener to "Edit" menu item, which is supposed to open the style file in an external editor: edit.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent actionEvent) { int i = table.getSelectedRow(); if (i == -1) return; ExternalFileType type = Globals.prefs.getExternalFileTypeByExt("jstyle"); String link = ((OOBibStyle)tableModel.getElementAt(i)).getFile().getPath(); try { if (type != null) Util.openExternalFileAnyFormat(new MetaData(), link, type); else Util.openExternalFileUnknown(frame, null, new MetaData(), link, new UnknownExternalFileType("jstyle")); } catch (IOException e) { e.printStackTrace(); } } }); diag = new JDialog(frame, Globals.lang("Styles"), true); styles = new BasicEventList<OOBibStyle>(); sortedStyles = new SortedList<OOBibStyle>(styles); // Create a preview panel for previewing styles: preview = new PreviewPanel(null, new MetaData(), ""); // Use the test entry from the Preview settings tab in Preferences: preview.setEntry(prevEntry);//PreviewPrefsTab.getTestEntry()); tableModel = new EventTableModel<OOBibStyle>(sortedStyles, new StyleTableFormat()); table = new JTable(tableModel); TableColumnModel cm = table.getColumnModel(); cm.getColumn(0).setPreferredWidth(100); cm.getColumn(1).setPreferredWidth(200); cm.getColumn(2).setPreferredWidth(80); selectionModel = new EventSelectionModel<OOBibStyle>(sortedStyles); table.setSelectionModel(selectionModel); table.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION); table.addMouseListener(new MouseAdapter() { @Override public void mousePressed(MouseEvent mouseEvent) { if (mouseEvent.isPopupTrigger()) tablePopup(mouseEvent); } @Override public void mouseReleased(MouseEvent mouseEvent) { if (mouseEvent.isPopupTrigger()) tablePopup(mouseEvent); } }); selectionModel.getSelected().addListEventListener(new EntrySelectionListener()); styleDir.getDocument().addDocumentListener(new DocumentListener() { public void insertUpdate(DocumentEvent documentEvent) { readStyles(); setDirectory.setSelected(true); } public void removeUpdate(DocumentEvent documentEvent) { readStyles(); setDirectory.setSelected(true); } public void changedUpdate(DocumentEvent documentEvent) { readStyles(); setDirectory.setSelected(true); } }); directFile.getDocument().addDocumentListener(new DocumentListener() { public void insertUpdate(DocumentEvent documentEvent) { chooseDirectly.setSelected(true); } public void removeUpdate(DocumentEvent documentEvent) { chooseDirectly.setSelected(true); } public void changedUpdate(DocumentEvent documentEvent) { chooseDirectly.setSelected(true); } }); contentPane.setTopComponent(new JScrollPane(table)); contentPane.setBottomComponent(preview); readStyles(); DefaultFormBuilder b = new DefaultFormBuilder(new FormLayout("fill:pref,4dlu,fill:150dlu,4dlu,fill:pref","")); b.append(useDefaultAuthoryear, 3); b.append(showDefaultAuthoryearStyle); b.nextLine(); b.append(useDefaultNumerical, 3); b.append(showDefaultNumericalStyle); b.nextLine(); b.append(chooseDirectly); b.append(directFile); b.append(browseDirectFile); b.nextLine(); b.append(setDirectory); b.append(styleDir); b.append(browseStyleDir); b.nextLine(); DefaultFormBuilder b2 = new DefaultFormBuilder(new FormLayout("fill:1dlu:grow", "fill:pref, fill:pref, fill:270dlu:grow")); b2.nextLine(); b2.append(new JLabel("<html>"+Globals.lang("This is the list of available styles. Select the one you want to use.")+"</html>")); b2.nextLine(); b2.append(contentPane); b.getPanel().setBorder(BorderFactory.createEmptyBorder(5,5,5,5)); b2.getPanel().setBorder(BorderFactory.createEmptyBorder(15,5,5,5)); diag.add(b.getPanel(), BorderLayout.NORTH); diag.add(b2.getPanel(), BorderLayout.CENTER); AbstractAction okListener = new AbstractAction() { public void actionPerformed(ActionEvent event) { if (!useDefaultAuthoryear.isSelected() && !useDefaultNumerical.isSelected()) { if (chooseDirectly.isSelected()) { File f = new File(directFile.getText()); if (!f.exists()) { JOptionPane.showMessageDialog(diag, Globals.lang("You must select either a valid style file, or use a default style."), Globals.lang("Style selection"), JOptionPane.ERROR_MESSAGE); return; } } else { if (table.getRowCount() == 0 || table.getSelectedRowCount() == 0) { JOptionPane.showMessageDialog(diag, Globals.lang("You must select either a valid style file, or use a default style."), Globals.lang("Style selection"), JOptionPane.ERROR_MESSAGE); return; } } } okPressed = true; storeSettings(); diag.dispose(); } }; ok.addActionListener(okListener); Action cancelListener = new AbstractAction() { public void actionPerformed(ActionEvent event) { diag.dispose(); } }; cancel.addActionListener(cancelListener); ButtonBarBuilder bb = new ButtonBarBuilder(); bb.addGlue(); bb.addButton(ok); bb.addButton(cancel); bb.addGlue(); bb.getPanel().setBorder(BorderFactory.createEmptyBorder(5,5,5,5)); diag.add(bb.getPanel(), BorderLayout.SOUTH); ActionMap am = bb.getPanel().getActionMap(); InputMap im = bb.getPanel().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); im.put(Globals.prefs.getKey("Close dialog"), "close"); am.put("close", cancelListener); im.put(KeyStroke.getKeyStroke("ENTER"), "enterOk"); am.put("enterOk", okListener); diag.pack(); diag.setLocationRelativeTo(frame); SwingUtilities.invokeLater(new Runnable() { public void run() { contentPane.setDividerLocation(contentPane.getSize().height-150); } }); } public void setVisible(boolean visible) { okPressed = false; diag.setVisible(visible); } /** * Read all style files or directories of style files indicated by the current * settings, and add the styles to the list of styles. */ private void readStyles() { table.clearSelection(); styles.getReadWriteLock().writeLock().lock(); styles.clear(); if (styleDir.getText().length() > 0) addStyles(styleDir.getText(), true); styles.getReadWriteLock().writeLock().unlock(); selectLastUsed(); } /** * This method scans the current list of styles, and looks for the styles * that was last used. If found, that style is selected. If not found, * the first style is selected provided there are >0 styles. */ private void selectLastUsed() { // Set the initial selection of the table: if (initSelection != null) { boolean found = false; for (int i=0; i < table.getRowCount(); i++) { if (((OOBibStyle)tableModel.getElementAt(i)).getFile().getPath(). equals(initSelection)) { table.setRowSelectionInterval(i,i); found = true; break; } } if (!found && (table.getRowCount() > 0)) table.setRowSelectionInterval(0,0); } else { if (table.getRowCount() > 0) table.setRowSelectionInterval(0,0); } } /** * If the string dir indicates a file, parse it and add it to the list of styles if * successful. If the string dir indicates a directory, parse all files looking like * style files, and add them. The parameter recurse determines whether we should * recurse into subdirectories. * @param dir the directory or file to handle. * @param recurse true indicates that we should recurse into subdirectories. */ private void addStyles(String dir, boolean recurse) { File dirF = new File(dir); if (dirF.isDirectory()) { File[] files = dirF.listFiles(); for (int i = 0; i < files.length; i++) { File file = files[i]; // If the file looks like a style file, parse it: if (!file.isDirectory() && (file.getName().endsWith(STYLE_FILE_EXTENSION))) { addSingleFile(file); } // If the file is a directory, and we should recurse, do: else if (file.isDirectory() && recurse) { addStyles(file.getPath(), recurse); } } } else { // The file wasn't a directory, so we simply parse it: addSingleFile(dirF); } } /** * Parse a single file, and add it to the list of styles if parse was successful. * @param file the file to parse. */ private void addSingleFile(File file) { try { OOBibStyle style = new OOBibStyle(file); // Check if the parse was successful before adding it: if (style.isValid() && !styles.contains(style)) styles.add(style); } catch (Exception e) { System.out.println("Unable to read style file: '"+file.getPath()+"'"); e.printStackTrace(); } } public void storeSettings() { OOBibStyle selected = getSelectedStyle(); Globals.prefs.putBoolean("ooUseDefaultAuthoryearStyle", useDefaultAuthoryear.isSelected()); Globals.prefs.putBoolean("ooUseDefaultNumericalStyle", useDefaultNumerical.isSelected()); Globals.prefs.putBoolean("ooChooseStyleDirectly", chooseDirectly.isSelected()); Globals.prefs.put("ooDirectFile", directFile.getText()); Globals.prefs.put("ooStyleDirectory", styleDir.getText()); if (chooseDirectly.isSelected()) { Globals.prefs.put("ooBibliographyStyleFile", directFile.getText()); } else if (setDirectory.isSelected() && (selected != null)) { Globals.prefs.put("ooBibliographyStyleFile", selected.getFile().getPath()); } } /** * Get the currently selected style. * @return the selected style, or null if no style is selected. */ public OOBibStyle getSelectedStyle() { if (selectionModel.getSelected().size() > 0) return selectionModel.getSelected().get(0); else return null; } private void setupPrevEntry() { prevEntry.setField("author", "Smith, Bill and Jones, Bob and Williams, Jeff"); prevEntry.setField("editor", "Taylor, Phil"); prevEntry.setField("title", "Title of the test entry for reference styles"); prevEntry.setField("volume", "34"); prevEntry.setField("year", "2008"); prevEntry.setField("journal", "BibTeX journal"); prevEntry.setField("publisher", "JabRef publishing"); prevEntry.setField("address", "Trondheim"); prevEntry.setField("www", "http://jabref.sf.net"); } static class StyleTableFormat implements TableFormat<OOBibStyle> { public int getColumnCount() { return 3; } public String getColumnName(int i) { switch (i) { case 0: return Globals.lang("Name"); case 1: return Globals.lang("Journals"); case 2: return Globals.lang("File"); default: return ""; } } public Object getColumnValue(OOBibStyle style, int i) { switch (i) { case 0: return style.getName(); case 1: return formatJournals(style.getJournals()); case 2: return style.getFile().getName(); default: return ""; } } private String formatJournals(Set<String> journals) { StringBuilder sb = new StringBuilder(""); for (Iterator<String> i = journals.iterator(); i.hasNext();) { sb.append(i.next()); if (i.hasNext()) sb.append(", "); } return sb.toString(); } } public boolean isOkPressed() { return okPressed; } protected void tablePopup(MouseEvent e) { popup.show(e.getComponent(), e.getX(), e.getY()); } protected void displayDefaultStyle(boolean authoryear) { try { // Read the contents of the default style file: URL defPath = authoryear ? JabRef.class.getResource(OpenOfficePanel.defaultAuthorYearStylePath) : JabRef.class.getResource(OpenOfficePanel.defaultNumericalStylePath); BufferedReader r = new BufferedReader(new InputStreamReader(defPath.openStream())); String line = null; StringBuilder sb = new StringBuilder(); while ((line = r.readLine()) != null) { sb.append(line); sb.append("\n"); } // Make a dialog box to display the contents: final JDialog dd = new JDialog(diag, Globals.lang("Default style"), true); JLabel header = new JLabel("<html>"+Globals.lang("The panel below shows the definition of the default style.") //+"<br>" +Globals.lang("If you want to use it as a template for a new style, you can copy the contents into a new .jstyle file") +"</html>"); header.setBorder(BorderFactory.createEmptyBorder(5,5,5,5)); dd.getContentPane().add(header, BorderLayout.NORTH); JTextArea ta = new JTextArea(sb.toString()); ta.setEditable(false); JScrollPane sp = new JScrollPane(ta); sp.setPreferredSize(new Dimension(700,500)); dd.getContentPane().add(sp, BorderLayout.CENTER); JButton ok = new JButton(Globals.lang("Ok")); ButtonBarBuilder bb = new ButtonBarBuilder(); bb.addGlue(); bb.addButton(ok); bb.addGlue(); bb.getPanel().setBorder(BorderFactory.createEmptyBorder(5,5,5,5)); dd.getContentPane().add(bb.getPanel(), BorderLayout.SOUTH); ok.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent actionEvent) { dd.dispose(); } }); dd.pack(); dd.setLocationRelativeTo(diag); dd.setVisible(true); } catch (IOException ex) { ex.printStackTrace(); } } /** * The listener for the Glazed list monitoring the current selection. * When selection changes, we need to update the preview panel. */ class EntrySelectionListener implements ListEventListener<OOBibStyle> { public void listChanged(ListEvent<OOBibStyle> listEvent) { if (listEvent.getSourceList().size() == 1) { OOBibStyle style = listEvent.getSourceList().get(0); initSelection = style.getFile().getPath(); preview.setLayout(style.getReferenceFormat("default")); // Update the preview's entry: contentPane.setDividerLocation(contentPane.getSize().height-150); SwingUtilities.invokeLater(new Runnable() { public void run() { preview.update(); preview.scrollRectToVisible(toRect); } }); } } } }