/* * Copyright 2010 Tim Miller * This file is part of PaperManager * PaperManager 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 3 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, see <http://www.gnu.org/licenses/>. */ package applications; import java.awt.BorderLayout; import java.awt.Desktop; import java.awt.Dimension; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.FilenameFilter; import java.io.IOException; import java.util.HashSet; import java.util.List; import java.util.Properties; import java.util.Set; import javax.swing.ButtonGroup; import javax.swing.DefaultListSelectionModel; import javax.swing.JButton; import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JRadioButton; import javax.swing.JScrollPane; import javax.swing.JSplitPane; import javax.swing.JTabbedPane; import javax.swing.JTable; import javax.swing.JTextArea; import javax.swing.JTextField; import javax.swing.JToolBar; import javax.swing.event.ListSelectionEvent; import javax.swing.table.AbstractTableModel; import javax.swing.table.TableModel; import org.apache.commons.io.FileUtils; import papers.Paper; import papers.PaperFileReader; import papers.PaperFileWriter; import tags.Tag; import ui.BibEditorDialog; import ui.FileDropHandler; import ui.MyFileChooser; import bib.BibtexFileReader; import bib.BibtexFileWriter; import filters.AuthorNameFilter; import filters.PaperStringFilter; import filters.TitleNameFilter; /* * This is started based on the TableDemo on the swing tutorial site. * TODO Search by tag... (large) * TODO Improve look on os x (file menu on menu bar instead of on app window) * TODO Be able to refresh list when new file is dragged in? * TODO Visual reminder to save summary (grayed out save button when fresh? greyed text area?) * TODO (low) Make division between gui elements thicker (prettier) */ public class PaperManager extends JPanel implements ActionListener, MouseListener, KeyListener{ private static final String ADD_CMD = "ADD"; private static final String RM_CMD = "RM"; private static final String LINK_CMD = "LINK"; private static final String OPEN_CMD = "OPEN"; private static final String IMP_CMD = "IMPORT"; private static final String EXP_CMD = "EXPORT"; private static final String WRITE_CMD = "WRITE"; private static final String NEW_TAG = "NEW_TAG"; private static final String EDIT_CMD = "EDIT"; // private static final String CANCEL_EDIT = "CANCEL"; private static final String SAVE_EDIT = "SAVE_EDIT"; private static final String EXIT = "EXIT"; private static final String DB_LOC_KEY = "DB_LOC"; private static final String EXPORT_DIR = "EXPORT_DIR"; private static final String SNIP_CMD = "ADD_SNIPPET"; private static final String FILTER_AUTHOR = "FILTER_AUTHOR"; private static final String FILTER_TITLE = "FILTER_TITLE"; private static String configurationFilename = "/Users/tmill/.papermanager"; String sorryMsg = "Sorry, this platform does not support " + "opening PDFs from within the table. " + "Everything else should work fine though!"; private List<Paper> fullList=null; private List<Paper> displayList=null; private HashSet<String> filesLinked=null; private static Properties props=null; private static File confFile = null; PaperFileWriter writer = null; PaperFileReader pfr = null; BibtexFileReader bibReader = null; BibtexFileWriter bibWriter = null; private PaperStringFilter paperFilter = null; JTable table; JTextArea summaryBox=null; JTextField tagField=null; JTextField filterField = null; ButtonGroup filterButtons = null; RefTableModel tModel; TagTableModel tagModel; String rootDir; Desktop desktop=null; private int LABEL_COL; private int TYPE_COL; private int VENUE_COL; private int PDF_COL; private JFrame parent; public PaperManager(String fn, JFrame p) { super(new BorderLayout()); try{ if(!Desktop.isDesktopSupported()){ // this is a problem on some platforms noDesktop(this); }else{ desktop = Desktop.getDesktop(); if(!desktop.isSupported(Desktop.Action.OPEN)){ noDesktop(this); desktop = null; } } }catch(UnsupportedOperationException e){ // apparently this is a possibility with older apis? noDesktop(this); } parent = p; rootDir = (new File(fn)).getParent(); // initialize data structures pfr = new PaperFileReader(fn); fullList = pfr.readFile(); displayList = fullList; writer = new PaperFileWriter(fn); tModel = new RefTableModel(); filesLinked = new HashSet(); // add toolbar and buttons JToolBar toolbar = new JToolBar("Tool buttons"); JButton button = new JButton(); button.setText(" + "); button.addActionListener(this); button.setActionCommand(ADD_CMD); toolbar.add(button); button = new JButton(); button.setText(" - "); button.addActionListener(this); button.setActionCommand(RM_CMD); toolbar.add(button); // put in a spacer so we don't accidentally hit remove! toolbar.addSeparator(new Dimension(100,10)); button = new JButton(); button.setText("Link"); button.addActionListener(this); button.setActionCommand(LINK_CMD); toolbar.add(button); button = new JButton(); button.setText("Open PDF"); button.addActionListener(this); button.setActionCommand(OPEN_CMD); toolbar.add(button); button = new JButton(); button.setText("Edit"); button.addActionListener(this); button.setActionCommand(EDIT_CMD); toolbar.add(button); toolbar.addSeparator(new Dimension(50,10)); toolbar.add(new JLabel("Filter")); filterField = new JTextField(20); // filterField.addActionListener(this); filterField.addKeyListener(this); toolbar.add(filterField); paperFilter = new AuthorNameFilter(fullList); filterButtons = new ButtonGroup(); JRadioButton authorButton = new JRadioButton("Author"); authorButton.setSelected(true); authorButton.addActionListener(this); authorButton.setActionCommand(FILTER_AUTHOR); JRadioButton titleButton = new JRadioButton("Title"); titleButton.addActionListener(this); titleButton.setActionCommand(FILTER_TITLE); filterButtons.add(authorButton); filterButtons.add(titleButton); toolbar.add(authorButton); toolbar.add(titleButton); toolbar.addSeparator(new Dimension(50,10)); button = new JButton(); button.setText("Export (bib)"); button.addActionListener(this); button.setActionCommand(EXP_CMD); toolbar.add(button); button = new JButton(); button.setText("Import (bib)"); button.addActionListener(this); button.setActionCommand(IMP_CMD); toolbar.add(button); button = new JButton(); button.setText("Add bib snippet"); button.addActionListener(this); button.setActionCommand(SNIP_CMD); toolbar.add(button); // add table table = new RefTable(tModel); table.addMouseListener(this); //Create the scroll pane and add the table to it. JScrollPane scrollPane = new JScrollPane(table); // create the editing box for summaries // pane containing text area and button for saving... summaryBox = new JTextArea(1,20); summaryBox.setLineWrap(true); summaryBox.setWrapStyleWord(true); JScrollPane summaryPane = new JScrollPane(summaryBox); JPanel summaryPanel = new JPanel(new BorderLayout()); summaryPanel.add(new JLabel("Summary"), BorderLayout.NORTH); summaryPanel.add(summaryPane, BorderLayout.CENTER); JButton writeButton = new JButton(); writeButton.setText("Save Summary"); writeButton.addActionListener(this); writeButton.setActionCommand(WRITE_CMD); summaryPanel.add(writeButton, BorderLayout.SOUTH); summaryPanel.setPreferredSize(new Dimension(200, 300)); tagModel = new TagTableModel(); JTable tagList = new JTable(tagModel); JPanel tagPanel = new JPanel(new BorderLayout()); tagField = new JTextField(); tagField.setActionCommand(NEW_TAG); tagField.addActionListener(this); tagPanel.add(new JScrollPane(tagList), BorderLayout.CENTER); tagPanel.add(tagField, BorderLayout.SOUTH); tagPanel.setPreferredSize(new Dimension(200, 300)); // make a split pane? JSplitPane sidePanel = new JSplitPane(JSplitPane.VERTICAL_SPLIT, summaryPanel, tagPanel); JPanel entryPanel = new JPanel(new BorderLayout()); JTabbedPane mainPane = new JTabbedPane(); //Add the scroll pane to this panel. entryPanel.add(toolbar, BorderLayout.NORTH); entryPanel.add(scrollPane, BorderLayout.CENTER); entryPanel.add(sidePanel, BorderLayout.EAST); mainPane.addTab("Bibtex Entries", entryPanel); JScrollPane docPanel = buildSecondPanel(); //= new JScrollPane(); // buildSecondPanel(); mainPane.addTab("Unbound Documents", docPanel); add(mainPane); } public void reloadList(){ // create a new list. probably just make its own list model } private JScrollPane buildSecondPanel(){ // fill the set of files linked to papers first... loadFilenames(); File dir = new File(rootDir); String[] files = dir.list(new FilenameFilter(){ public boolean accept(File f, String s){ return (s.toLowerCase().endsWith("pdf")) && !(filesLinked.contains(s)); } }); JList list = new JList(files); list.setTransferHandler(new FileDropHandler(rootDir, this)); // for(String fn : files){ // if(!filesLinked.contains(fn)){ // list.add(new JLabel(fn)); // } // } return new JScrollPane(list); } private void loadFilenames() { for(Paper paper : displayList){ if(paper.getFile() != null && !paper.getFile().getName().equals("")){ filesLinked.add(paper.getFile().getName()); } } } @Override public void actionPerformed(ActionEvent arg0) { if(arg0.getActionCommand().equals(ADD_CMD)){ if(fullList == displayList){ // System.err.println("Add button has been pressed!"); displayList.add(new Paper()); editPaper(displayList.size()-1); // BibEditorDialog dialog = new BibEditorDialog( // tModel.loadData(); // tModel.fireTableDataChanged(); }else{ JOptionPane.showMessageDialog(parent, "This button does not work in filter mode!", "Warning", JOptionPane.WARNING_MESSAGE); } }else if(arg0.getActionCommand().equals(RM_CMD)){ // System.err.println("Remove button has been pressed!"); if(displayList == fullList){ int row = table.getSelectedRow(); displayList.remove(row); tModel.loadData(); tModel.fireTableDataChanged(); writer.writeFile(fullList); }else{ JOptionPane.showMessageDialog(parent, "This button does not work in filter mode!", "Warning", JOptionPane.WARNING_MESSAGE); } }else if(arg0.getActionCommand().equals(OPEN_CMD)){ // System.err.println("Open button has been pressed."); if(desktop != null){ File f = displayList.get(table.getSelectedRow()).getFile(); if(f != null){ try { System.err.println("Opening: " + f); File fullP = new File(rootDir + "/" + f.getName()); if(fullP.exists()){ desktop.open(fullP); } } catch (IOException e) { JOptionPane.showMessageDialog(this, "Sorry, could not open file!", "Error", ERROR); // e.printStackTrace(); } }else{ System.err.println("No file associated with that paper!"); } } }else if(arg0.getActionCommand().equals(IMP_CMD)){ // System.err.println("Import command triggered"); if(fullList == displayList){ String fn=""; if(bibReader == null) bibReader = new BibtexFileReader(); JFileChooser fc = new JFileChooser(); fc.setFileSelectionMode(JFileChooser.FILES_ONLY); int ret = fc.showOpenDialog(table); if(ret == JFileChooser.APPROVE_OPTION){ fn = fc.getSelectedFile().getPath(); try { bibReader.readBibtext(FileUtils.readFileToString(fc.getSelectedFile()), fullList); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } tModel.loadData(); tModel.fireTableDataChanged(); writer.writeFile(fullList); } }else{ JOptionPane.showMessageDialog(parent, "This button does not work in filter mode!", "Warning", JOptionPane.WARNING_MESSAGE); } }else if(arg0.getActionCommand().equals(SNIP_CMD)){ if(fullList == displayList){ if(bibReader == null) bibReader = new BibtexFileReader(); // JPanel textPanel = new JPanel(); JTextArea textArea = new JTextArea(10, 50); textArea.setEditable(true); // textPanel.add(textArea); int ret = JOptionPane.showConfirmDialog(null, textArea, "Paste in bib entries:", JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE); if(ret == JOptionPane.CANCEL_OPTION) return; bibReader.readBibtext(textArea.getText(), fullList); tModel.loadData(); tModel.fireTableDataChanged(); writer.writeFile(fullList); }else{ JOptionPane.showMessageDialog(parent, "This button does not work in filter mode!", "Warning", JOptionPane.WARNING_MESSAGE); } }else if(arg0.getActionCommand().equals(EXP_CMD)){ // System.err.println("Export command triggered"); String fn=""; if(bibWriter == null) bibWriter = new BibtexFileWriter(); MyFileChooser fc = new MyFileChooser(); fc.setFileSelectionMode(JFileChooser.FILES_ONLY); if(props.containsKey(EXPORT_DIR)){ fc.setSelectedFile(new File((String)props.get(EXPORT_DIR))); } int ret = fc.showSaveDialog(table); if(ret == JFileChooser.APPROVE_OPTION){ fn = fc.getSelectedFile().getPath(); props.setProperty(EXPORT_DIR, fn); boolean success = bibWriter.writeFile(fn, displayList); if(!success){ JOptionPane.showMessageDialog(this, "ERROR Exporting bib file: Please see stack trace."); } } }else if(arg0.getActionCommand().equals(WRITE_CMD)){ // System.err.println("Write command triggered"); int row = table.getSelectedRow(); Paper selPaper = displayList.get(row); // selPaper.setField("summary", summaryBox.getText()); selPaper.setSummary(summaryBox.getText()); writer.writeFile(fullList); }else if(arg0.getActionCommand().equals(NEW_TAG)){ // System.err.println("New tag command triggered"); int row = table.getSelectedRow(); tagModel.addTag(row, tagField.getText()); tagField.setText(""); }else if(arg0.getActionCommand().equals(LINK_CMD)){ // System.err.println("Link command triggered"); JFileChooser fc = new JFileChooser(rootDir); fc.setFileSelectionMode(JFileChooser.FILES_ONLY); int ret = fc.showOpenDialog(table); if(ret == JFileChooser.APPROVE_OPTION){ int row = table.getSelectedRow(); displayList.get(row).setFile(fc.getSelectedFile()); tModel.loadData(); tModel.fireTableCellUpdated(row, PDF_COL); writer.writeFile(fullList); } }else if(arg0.getActionCommand().equals(EDIT_CMD)){ // System.err.println("Edit command triggered"); int row = table.getSelectedRow(); if(row >= 0){ // editBibentry(paperList.get(row));/ editPaper(row); } // }else if(arg0.getActionCommand().equals(CANCEL_EDIT)){ // System.err.println("Cancel pressed."); // }else if(arg0.getActionCommand().equals(SAVE_EDIT)){ // System.err.println("Save pressed."); }else if(arg0.getActionCommand().equals(EXIT)){ try { props.store(new FileOutputStream(confFile), null); } catch (FileNotFoundException e) { fatal(e, "ERROR: Could not find file: " + confFile.getAbsolutePath()); } catch (IOException e) { fatal(e, "ERROR: Cannot store properties in file: " + confFile.getAbsolutePath()); } System.exit(0); }else if(arg0.getActionCommand().equals(FILTER_AUTHOR)){ if(!(paperFilter instanceof AuthorNameFilter)){ filterField.setText(""); paperFilter = new AuthorNameFilter(fullList); tModel.loadData(); tModel.fireTableDataChanged(); } }else if(arg0.getActionCommand().equals(FILTER_TITLE)){ if(!(paperFilter instanceof TitleNameFilter)){ filterField.setText(""); paperFilter = new TitleNameFilter(fullList); tModel.loadData(); tModel.fireTableDataChanged(); } }else{ System.err.println("Unknown event: " + arg0.getActionCommand()); } } private void editPaper(int row){ BibEditorDialog dialog = new BibEditorDialog(displayList.get(row).getEntry(), parent); dialog.setVisible(true); if(dialog.isDirty()){ tModel.loadData(); tModel.fireTableDataChanged(); writer.writeFile(fullList); } } class RefTable extends JTable{ public RefTable(TableModel tm){ super(tm); } @Override public void valueChanged(ListSelectionEvent e) { super.valueChanged(e); if(!e.getValueIsAdjusting()){ // System.err.println("Value changed: " + e); DefaultListSelectionModel dlsm = (DefaultListSelectionModel) e.getSource(); int row = dlsm.getAnchorSelectionIndex(); if(row >= 0){ Paper curr = displayList.get(row); summaryBox.setText(curr.getSummary()); tagModel.setData(curr.getTags()); } } } public String getToolTipText(MouseEvent e) { String tip = null; java.awt.Point p = e.getPoint(); int rowIndex = rowAtPoint(p); int colIndex = columnAtPoint(p); tip = tModel.getValueAt(rowIndex, colIndex).toString(); return tip; } } class RefTableModel extends AbstractTableModel{ private String[] columnNames = {"Label", "Author", "Title", "Type", "Venue", "Year", "PDF?"}; private Object[][] data = null; public RefTableModel(){ loadData(); } private void loadData(){ data = new Object[displayList.size()][]; int j; for(int i = 0; i < data.length; i++){ j = 0; data[i] = new Object[columnNames.length]; LABEL_COL = j; data[i][j++] = displayList.get(i).getLabel(); data[i][j++] = displayList.get(i).getField("author"); data[i][j++] = displayList.get(i).getField("title"); TYPE_COL = j; data[i][j++] = displayList.get(i).getType(); VENUE_COL = j; data[i][j++] = displayList.get(i).getVenue(); data[i][j++] = displayList.get(i).getField("year"); PDF_COL = j; data[i][j++] = (displayList.get(i).getFile() == null) ? "no" : "yes"; } } /* * Don't need to implement this method unless your table's * data can change. */ public void setValueAt(Object value, int row, int col) { data[row][col] = value; //if(col == 0 || col == 1 || col == 4){ if(col == LABEL_COL){ displayList.get(row).setLabel((String) value); }else if(col == TYPE_COL){ displayList.get(row).setType((String) value); }else if(col == VENUE_COL){ displayList.get(row).setVenue((String) value); }else{ displayList.get(row).setField(columnNames[col].toLowerCase(), (String) value); } writer.writeFile(fullList); fireTableCellUpdated(row, col); } public int getColumnCount() { return columnNames.length; } public int getRowCount() { return data.length; } public String getColumnName(int col) { return columnNames[col]; } public Object getValueAt(int row, int col) { return data[row][col]; } /* * JTable uses this method to determine the default renderer/ * editor for each cell. If we didn't implement this method, * then the last column would contain text ("true"/"false"), * rather than a check box. * Special case fixes problem of empty cells causing null pointer exceptions */ public Class getColumnClass(int c) { if(getValueAt(0,c)== null){ return (new String("")).getClass(); } return getValueAt(0, c).getClass(); } /* * Don't need to implement this method unless your table's * editable. */ public boolean isCellEditable(int row, int col) { //Note that the data/cell address is constant, //no matter where the cell appears onscreen. if(col == PDF_COL || col == TYPE_COL) return false; return true; } } class TagTableModel extends AbstractTableModel{ String[] columnNames = {"Current Tags"}; private Object[][] data = null; Set<Tag> tags=null; public void setData(Set<Tag> t){ tags = t; data = new Object[tags.size()][1]; Object[] temp = tags.toArray(); for(int i = 0; i < temp.length; i++){ data[i][0] = temp[i]; } fireTableDataChanged(); } public void addTag(int row, String text) { displayList.get(row).addTag(text); setData(displayList.get(row).getTags()); writer.writeFile(fullList); } public void setValueAt(Object newData, int row, int col){ // System.err.println("Setting row " + row + " to " + newData); Tag oldTag = (Tag) data[row][0]; System.err.println("Old tag: " + oldTag); oldTag.setTag(newData.toString()); data[row][0] = oldTag; writer.writeFile(fullList); } @Override public int getColumnCount() { return 1; } @Override public int getRowCount() { return tags == null ? 0 : tags.size(); } @Override public Object getValueAt(int rowIndex, int columnIndex) { return data[rowIndex][columnIndex]; } public boolean isCellEditable(int row, int col){ return true; } public String getColumnName(int col) { return columnNames[0]; } } private void noDesktop(JPanel parent){ JOptionPane.showMessageDialog(parent, sorryMsg); } /** * Create the GUI and show it. For thread safety, * this method should be invoked from the * event-dispatching thread. */ private static void createAndShowGUI() { //Create and set up the window. JFrame frame = new JFrame("Paper Manager"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // first read conf file (or create if doesn't exist) configurationFilename = System.getProperty("user.home") + "/.papermanager"; confFile = new File(configurationFilename); // if it doesn't exist create it (have to ask for db_dir location) if(!confFile.exists()){ try { confFile.createNewFile(); } catch (IOException e) { fatal(e, "Error creating configuration file: " + confFile.getPath()); } } // load properties whether or not the file has any (like if we just created it above) props = new Properties(); try { props.load(new FileInputStream(configurationFilename)); } catch (FileNotFoundException e1) { fatal(e1, "ERROR: Could not find file: " + configurationFilename); } catch (IOException e1) { fatal(e1, "ERROR: Could not handle file: " + configurationFilename); } // if we have no properties (only property right now is path to folder w/ pdfs and papers.xml) // then we bring up a file dialog... this is tricky across different platforms i guess? if(!props.containsKey(DB_LOC_KEY)){ String dbDir=""; JFileChooser fileChooser = new JFileChooser(); fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); int ret = fileChooser.showOpenDialog(null); if(ret == JFileChooser.APPROVE_OPTION){ dbDir = fileChooser.getSelectedFile().getPath(); }else{ JOptionPane.showMessageDialog(null, "Error: No directory selected. Please figure out what directory you would like and re-run.", "Alert", JOptionPane.WARNING_MESSAGE); System.exit(0); } props.setProperty(DB_LOC_KEY, dbDir); try { props.store(new FileOutputStream(confFile), null); } catch (FileNotFoundException e) { fatal(e, "ERROR: Could not find file: " + confFile.getAbsolutePath()); } catch (IOException e) { fatal(e, "ERROR: Cannot store properties in file: " + confFile.getAbsolutePath()); } } // read configuration file String dbDir = props.getProperty(DB_LOC_KEY); //Create and set up the content pane. File dbFile = new File(dbDir + "/papers.xml"); // File dbFile = new File("papers_text.xml"); if(!dbFile.exists()){ try { dbFile.createNewFile(); } catch (IOException e) { fatal(e, "ERROR: Could not create file: " + dbFile.getPath()); } } PaperManager newContentPane = new PaperManager(dbFile.getPath(), frame); JMenuBar menubar = new JMenuBar(); JMenu fileMenu = new JMenu("File"); JMenuItem miImport = new JMenuItem("Import .bib file"); miImport.setActionCommand(IMP_CMD); miImport.addActionListener(newContentPane); JMenuItem miExport = new JMenuItem("Export .bib file"); miExport.setActionCommand(EXP_CMD); miExport.addActionListener(newContentPane); JMenuItem miLast = new JMenuItem("Exit"); miLast.setActionCommand(EXIT); miLast.addActionListener(newContentPane); fileMenu.add(miImport); fileMenu.add(miExport); fileMenu.add(miLast); JMenu editMenu = new JMenu("Edit"); JMenuItem miEdit = new JMenuItem("Edit paper info"); miEdit.setActionCommand(EDIT_CMD); miEdit.addActionListener(newContentPane); editMenu.add(miEdit); menubar.add(fileMenu); menubar.add(editMenu); frame.setJMenuBar(menubar); newContentPane.setOpaque(true); //content panes must be opaque frame.setContentPane(newContentPane); //Display the window. frame.pack(); frame.setVisible(true); } public static void main(String[] args) { //Schedule a job for the event-dispatching thread: //creating and showing this application's GUI. javax.swing.SwingUtilities.invokeLater(new Runnable() { public void run() { createAndShowGUI(); } }); } private static void fatal(Exception e, String s){ e.printStackTrace(); System.err.println(s); } @Override public void mouseClicked(MouseEvent arg0) { if(arg0.getButton() == MouseEvent.BUTTON3){ // right click JPopupMenu rcMenu = new JPopupMenu(); JMenuItem editMenu = new JMenuItem("Edit entry"); editMenu.addActionListener(this); editMenu.setActionCommand(EDIT_CMD); JMenuItem openMenu = new JMenuItem("Open paper"); openMenu.addActionListener(this); openMenu.setActionCommand(OPEN_CMD); JMenuItem linkMenu = new JMenuItem("Link paper to PDF"); linkMenu.addActionListener(this); linkMenu.setActionCommand(LINK_CMD); rcMenu.add(editMenu); rcMenu.add(openMenu); rcMenu.add(linkMenu); rcMenu.setLocation(arg0.getPoint()); rcMenu.show(arg0.getComponent(), arg0.getX(), arg0.getY()); } } @Override public void mouseEntered(MouseEvent arg0) {} @Override public void mouseExited(MouseEvent arg0) {} @Override public void mousePressed(MouseEvent arg0) {} @Override public void mouseReleased(MouseEvent arg0) {} @Override public void keyPressed(KeyEvent arg0) { // ignore and wait for "released" } @Override public void keyReleased(KeyEvent arg0) { String textSoFar = filterField.getText(); if(textSoFar.length() == 0) displayList = fullList; else displayList = paperFilter.filterList(textSoFar); tModel.loadData(); tModel.fireTableDataChanged(); } @Override public void keyTyped(KeyEvent arg0) { // ignore and wait for "released" } }