/* * jMemorize - Learning made easy (and fun) - A Leitner flashcards tool * Copyright(C) 2004-2008 Riad Djemili and 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 1, 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., 675 Mass Ave, Cambridge, MA 02139, USA. */ package jmemorize.gui.swing.widgets; import java.awt.Color; import java.awt.Component; import java.awt.FontMetrics; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.LinkedList; import java.util.List; import java.util.prefs.Preferences; import javax.swing.Icon; import javax.swing.JCheckBoxMenuItem; import javax.swing.JComponent; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPopupMenu; import javax.swing.JTable; import javax.swing.SwingConstants; import javax.swing.SwingUtilities; import javax.swing.event.ChangeEvent; import javax.swing.event.ListSelectionEvent; import javax.swing.event.TableColumnModelEvent; import javax.swing.table.AbstractTableModel; import javax.swing.table.DefaultTableCellRenderer; import javax.swing.table.DefaultTableColumnModel; import javax.swing.table.JTableHeader; import javax.swing.table.TableCellRenderer; import javax.swing.table.TableColumn; import javax.swing.table.TableColumnModel; import jmemorize.core.Card; import jmemorize.core.Category; import jmemorize.core.Main; import jmemorize.core.Settings; import jmemorize.core.Main.ProgramEndObserver; import jmemorize.gui.LC; import jmemorize.gui.Localization; import jmemorize.gui.swing.CardFont; import jmemorize.gui.swing.CardStatusIcons; import jmemorize.gui.swing.SelectionProvider; import jmemorize.gui.swing.CardFont.FontAlignment; import jmemorize.gui.swing.CardFont.FontType; import jmemorize.gui.swing.actions.LearnAction; import jmemorize.gui.swing.actions.edit.AddCardAction; import jmemorize.gui.swing.actions.edit.CopyAction; import jmemorize.gui.swing.actions.edit.CutAction; import jmemorize.gui.swing.actions.edit.EditCardAction; import jmemorize.gui.swing.actions.edit.PasteAction; import jmemorize.gui.swing.actions.edit.RemoveAction; import jmemorize.gui.swing.actions.edit.ResetCardAction; import jmemorize.gui.swing.frames.MainFrame; import jmemorize.gui.swing.panels.StatusBar; import jmemorize.util.Arrow; import jmemorize.util.PreferencesTool; import jmemorize.util.ReverseOrder; import jmemorize.util.TimeSpan; /** * @author djemili */ public class CardTable extends JTable implements Settings.CardFontObserver, SelectionProvider, ProgramEndObserver { /** * A TableCellRenderer that displays card sides. It uses the specified fonts * for front/flip sides. */ private class SideRenderer extends DefaultTableCellRenderer { private CardFont m_font; public SideRenderer(CardFont font) { m_font = font; } public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { Component component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); if (m_font != null) { component.setFont(m_font.getFont()); if (component instanceof JLabel) { JLabel label = (JLabel)component; if (m_font.getAlignment() == FontAlignment.LEFT) label.setHorizontalAlignment(SwingConstants.LEADING); else if (m_font.getAlignment() == FontAlignment.CENTER) label.setHorizontalAlignment(SwingConstants.CENTER); else if (m_font.getAlignment() == FontAlignment.RIGHT) label.setHorizontalAlignment(SwingConstants.TRAILING); } } return component; } } /** * A TableCellRenderer that displays cells with Strings and a specific * string suffix. */ private class TextRenderer extends DefaultTableCellRenderer { private String m_suffix; public TextRenderer(String suffix) { m_suffix = suffix; } public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { return super.getTableCellRendererComponent(table, value + m_suffix, isSelected, hasFocus, row, column); } } /** * A TableCellRenderer that renders normal cells with dates. */ private class DateRenderer extends DefaultTableCellRenderer { private String m_nullString; public DateRenderer(String nullString) { m_nullString = nullString; } protected void setValue(Object value) { setText(value == null ? m_nullString : Localization.SHORT_DATE_FORMATER.format(value)); } } /** * A TableCellRenderer that renders cells which show expiration dates. This * renderer displays a little icon with every date. The icon is chosen * according to the expiration state (e.g. expired, unlearned, etc.). */ private class DateExpiredRenderer extends DefaultTableCellRenderer { public void setValue(Object value) { // if not learned if (value == null) { setText(Localization.get("CardTable.NOT_LEARNED")); //$NON-NLS-1$ } else { setText(Localization.SHORT_DATE_FORMATER.format(value)); } Date expiration = (Date)value; setIcon(CardStatusIcons.getInstance().getCardIcon(expiration)); } } /** * A Comparator that orders cards according to the specified model index. */ private class CardComparator implements Comparator<Card> { private int m_modelIndex; public CardComparator(int modelIndex) { m_modelIndex = modelIndex; } /** * @see java.util.Comparator */ public int compare(Card arg0, Card arg1) { Comparable col0 = (Comparable)getValue(arg0, m_modelIndex); Comparable col1 = (Comparable)getValue(arg1, m_modelIndex); if (col0 == null) { return col1 == null ? 0 : -1; } else { return col1 == null ? 1 : col0.compareTo(col1); } } } /** * Copied from Java Sample. */ private class SortableHeaderRenderer implements TableCellRenderer { private TableCellRenderer tableCellRenderer; public SortableHeaderRenderer(TableCellRenderer tableCellRenderer) { this.tableCellRenderer = tableCellRenderer; } public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { Component c = tableCellRenderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); if (c instanceof JLabel) { JLabel l = (JLabel)c; l.setHorizontalTextPosition(SwingConstants.LEFT); int modelColumn = table.convertColumnIndexToModel(column); if (modelColumn == m_tableModel.getSortingColumn()) { l.setIcon(m_tableModel.getSortingDir() == ViewModel.ASCENDING ? m_ascendingArrow : m_descendingArrow); } else { l.setIcon(null); } } return c; } } public class ViewModel extends AbstractTableModel { public static final int ASCENDING = 0; public static final int DESCENDING = 1; private List<Card> m_cards = new ArrayList<Card>(); // HACK currently this is only needed for transferhandlers private Category m_category; private int m_orderModelIndex; private int m_orderDir; public Category getCategory() { return m_category; } public void setCards(List<Card> cards, Category category) { m_cards = cards; m_category = category; resort(); updateCardCountStatusBar(); } /** * Set the model column index and direction by which this table should * be sorted. * * @param modelIndex The model index by which the sorting should happen. * @param direction Can be in ASCENDING or DESCENDING order. */ public void setSorting(int modelIndex, int direction) { m_orderModelIndex = modelIndex; m_orderDir = direction; resort(); } public List<Card> getCards() { return m_cards; } /** * @see javax.swing.table.TableModel#getRowCount() */ public int getRowCount() { return m_cards != null ? m_cards.size() : 0; } /** * @see javax.swing.table.TableModel#getColumnCount() */ public int getColumnCount() { return COLUMN_NAMES.length; } /** * @see javax.swing.table.AbstractTableModel#getColumnName(int) */ public String getColumnName(int column) { return COLUMN_NAMES[column]; } /** * @see javax.swing.table.AbstractTableModel#isCellEditable(int, int) */ public boolean isCellEditable(int rowIndex, int columnIndex) { return false; } /** * @see javax.swing.table.TableModel#getValueAt(int, int) */ public Object getValueAt(int rowIndex, int columnIndex) { Card card = (Card)m_cards.get(rowIndex); return getValue(card, columnIndex); } /** * @return Returns the orderColumn. */ public int getSortingColumn() { return m_orderModelIndex; } /** * @return Returns the orderDir. */ public int getSortingDir() { return m_orderDir; } private void resort() { if (m_cards != null) //CHECK { Comparator<Card> comparator = new CardComparator(m_orderModelIndex); Collections.sort(m_cards, m_orderDir == ASCENDING ? comparator : new ReverseOrder<Card>(comparator)); fireTableDataChanged(); } } } // column enum public static final int COLUMN_FRONTSIDE = 0; public static final int COLUMN_BACKSIDE = 1; public static final int COLUMN_DECK = 2; public static final int COLUMN_CATEGORY = 3; public static final int COLUMN_PATH = 4; public static final int COLUMN_CREATED = 5; public static final int COLUMN_TESTED = 6; public static final int COLUMN_EXPIRES = 7; public static final int COLUMN_RATIO = 8; public static final int COLUMN_MODIFIED = 9; // preferences keys private static final String PREF_SORT = "sort-by"; //$NON-NLS-1$ private static final String PREF_SORT_DIR = "sort-dir"; //$NON-NLS-1$ private static final String PREF_WIDTHS = "widths"; //$NON-NLS-1$ private static final String PREF_COLUMNS = "columns"; //$NON-NLS-1$ private static final String[] COLUMN_NAMES = { Localization.get(LC.FRONTSIDE), Localization.get(LC.FLIPSIDE), Localization.get(LC.DECK), Localization.get(LC.CATEGORY), Localization.get(LC.CARDTABLE_PATH), Localization.get(LC.CREATED), Localization.get(LC.LAST_TEST), Localization.get(LC.EXPIRES), Localization.get(LC.PASSED), Localization.get(LC.MODIFIED)}; private static final int DEFAULT_COLUMN_WIDTH = 170; // widgets private JCheckBoxMenuItem[] m_checkBoxItems = new JCheckBoxMenuItem[COLUMN_NAMES.length]; private JPopupMenu m_headerMenu = new JPopupMenu( Localization.get("CardTable.COLUMNS")); //$NON-NLS-1$ private JPopupMenu m_cardMenu; private StatusBar m_statusBar; // icons private Icon m_ascendingArrow = new Arrow(false, 17); private Icon m_descendingArrow = new Arrow(true, 17); /** background color for odd rows in table */ private Color m_highlight = new Color(0xF4, 0xF4, 0xF3); private ViewModel m_tableModel = new ViewModel(); private int[] m_columns; private int[] m_defaultColumns; private int[] m_columnWidths = new int[COLUMN_NAMES.length]; private CardFont m_frontSideFont; private CardFont m_flipSideFont; private Preferences m_prefs; private int m_minTableRowHeight; private JFrame m_frame; private List<SelectionObserver> m_selectionListeners = new LinkedList<SelectionObserver>(); /** * Creates a new CardTable which can be used to show a list of cards. * * @param the frame that should be used to display modal dialogs. * @param prefs the Preferences object which should be used to load/store * preferences of this card table. * @param defaultColumns if columns can't be loaded from preferences, * display these columns. */ public CardTable(JFrame frame, Preferences prefs, int[] defaultColumns) { m_frame = frame; m_cardMenu = buildCardContextMenu(); m_prefs = prefs; m_defaultColumns = defaultColumns; m_minTableRowHeight = getRowHeight(); setModel(m_tableModel); buildHeaderMenu(); getTableHeader().addMouseListener(new MouseAdapter(){ public void mouseClicked(MouseEvent e) { headerClicked(e); } }); hookCardContextMenu(this); loadFromPreferences(); setTransferHandler(MainFrame.TRANSFER_HANDLER); setDragEnabled(true); setShowGrid(false); setAutoResizeMode(JTable.AUTO_RESIZE_OFF); // we add the listener here. removal is done from outside if suitable Settings.addCardFontObserver(this); Settings.setCardFont(this, FontType.TABLE_FRONT, FontType.TABLE_FLIP); Main.getInstance().addProgramEndObserver(this); } public void setColumns(int[] columns) { m_columns = columns; // create and set tablecolumn model TableColumnModel columnModel = new DefaultTableColumnModel(); for (int i = 0; i < columns.length; i++) { if (columns[i] < 0 || columns[i] > COLUMN_NAMES.length) { continue; } TableColumn column = new TableColumn(columns[i]); column.setHeaderValue(COLUMN_NAMES[columns[i]]); TableCellRenderer defaultRenderer = getTableHeader().getDefaultRenderer(); column.setHeaderRenderer(new SortableHeaderRenderer(defaultRenderer)); if (m_columnWidths[columns[i]] > 0) { column.setPreferredWidth(m_columnWidths[columns[i]]); } else { column.setPreferredWidth(DEFAULT_COLUMN_WIDTH); } switch (columns[i]) { case COLUMN_FRONTSIDE: column.setCellRenderer(new SideRenderer(m_frontSideFont)); break; case COLUMN_BACKSIDE: column.setCellRenderer(new SideRenderer(m_flipSideFont)); break; case COLUMN_CREATED: // fall through case COLUMN_MODIFIED: column.setCellRenderer(new DateRenderer("-")); //$NON-NLS-1$ break; case COLUMN_EXPIRES: column.setCellRenderer(new DateExpiredRenderer()); break; case COLUMN_TESTED: column.setCellRenderer(new DateRenderer( Localization.get("CardTable.NOT_LEARNED"))); //$NON-NLS-1$ break; case COLUMN_RATIO: column.setCellRenderer(new TextRenderer("%")); break; default: column.setCellRenderer(new DefaultTableCellRenderer()); } columnModel.addColumn(column); } setColumnModel(columnModel); } public void setStatusBar(StatusBar statusBar) { m_statusBar = statusBar; if (m_statusBar != null) { updateCardCountStatusBar(); updateSelectedCardCountStatusBar(); } } /* (non-Javadoc) * @see jmemorize.gui.swing.SelectionProvider */ public List<Card> getRelatedCards() { return m_tableModel.getCards(); } /* (non-Javadoc) * @see jmemorize.gui.swing.SelectionProvider */ public Category getCategory() { return m_tableModel.getCategory(); } /* (non-Javadoc) * @see jmemorize.gui.swing.SelectionProvider */ public List<Category> getSelectedCategories() { return null; } /* (non-Javadoc) * @see jmemorize.gui.swing.SelectionProvider */ public JComponent getDefaultFocusOwner() { return this; } public JFrame getFrame() { return m_frame; } /* (non-Javadoc) * @see jmemorize.gui.swing.SelectionProvider */ public void addSelectionObserver(SelectionObserver observer) { m_selectionListeners.add(observer); } /* (non-Javadoc) * @see jmemorize.gui.swing.SelectionProvider */ public void removeSelectionObserver(SelectionObserver observer) { m_selectionListeners.remove(observer); } /** * @return the ViewModel for this table. */ public ViewModel getView() // TODO replace by cardprovider { return m_tableModel; } /** * @return all selected cards or <code>null</code> no cards are selected. * * @see jmemorize.gui.swing.SelectionProvider */ public List<Card> getSelectedCards() { if (m_tableModel == null) { return null; } List<Card> cards = m_tableModel.getCards(); List<Card> selection = new ArrayList<Card>(cards.size()); int rows[] = getSelectedRows(); for (int i = 0; i < rows.length; i++) { selection.add(cards.get(rows[i])); } return selection.size() > 0 ? selection : null; } /* * Is called when a listSelection occured. * * @see javax.swing.JTable */ public void valueChanged(ListSelectionEvent e) { super.valueChanged(e); updateSelectedCardCountStatusBar(); // this can be called while constructing super class. in that case our // variables wouldn't yet be set. if (!e.getValueIsAdjusting() && m_selectionListeners != null) { for (SelectionObserver listener : m_selectionListeners) { listener.selectionChanged(this); } } } /* * @see javax.swing.JTable */ public String getToolTipText(MouseEvent event) { /* this can be called while constructing super class. in that case our * variables wouldn't yet be set. */ if (m_columns == null || m_tableModel == null) { return ""; } int row = rowAtPoint(event.getPoint()); int col = columnAtPoint(event.getPoint()); Card card = (Card)m_tableModel.getCards().get(row); int modelIndex = m_columns[col]; switch (modelIndex) { case COLUMN_CATEGORY: return card.getCategory().getPath(); case COLUMN_CREATED: // fall-through case COLUMN_MODIFIED: Date date = modelIndex == COLUMN_CREATED ? card.getDateCreated() : card.getDateModified(); return ((date != null) ? TimeSpan.format(new Date(), date) : null); case COLUMN_TESTED: // fall-through case COLUMN_EXPIRES: date = modelIndex == COLUMN_TESTED ? card.getDateTested() : card.getDateExpired(); return ((date != null) ? TimeSpan.format(new Date(), date) : Localization.get("CardTable.NO_DATE_DESC")); //$NON-NLS-1$ case COLUMN_RATIO: return card.getTestsPassed() + " / " + card.getTestsTotal(); default: return null; } } /* * @see javax.swing.JTable */ public void columnMarginChanged(ChangeEvent e) { // CHECK replace by application exit behaviour super.columnMarginChanged(e); /* this can be called while constructing super class. in that case our * variables wouldn't yet be set. */ if (m_columns == null || m_columnWidths == null) { return; } for (int i = 0; i < m_columns.length; i++) { TableColumn column = getColumnModel().getColumn(i); m_columnWidths[column.getModelIndex()] = column.getWidth(); } } /* * @see javax.swing.JTable */ public void columnMoved(TableColumnModelEvent evt) { super.columnMoved(evt); /* this can be called while constructing super class. in that case our * variables wouldn't yet be set. */ if (m_columns == null) { return; } int[] columns = new int[m_columns.length]; for (int i = 0; i < columns.length; i++) { TableColumn column = getColumnModel().getColumn(i); columns[i] = column.getModelIndex(); } m_columns = columns; } /* * @see jmemorize.core.Settings.CardFontObserver */ public void fontChanged(FontType type, CardFont font) { if (type == FontType.TABLE_FRONT) m_frontSideFont = font; else if (type == FontType.TABLE_FLIP) m_flipSideFont = font; else return; if (m_frontSideFont == null || m_flipSideFont == null) return; FontMetrics metrics = getFontMetrics(m_frontSideFont.getFont()); int h = metrics.getHeight(); metrics = getFontMetrics(m_flipSideFont.getFont()); if (metrics.getHeight() > h) h = metrics.getHeight(); setRowHeight(h > m_minTableRowHeight ? h : m_minTableRowHeight); setColumns(m_columns); //HACK } /* (non-Javadoc) * @see jmemorize.core.Main.ProgramEndObserver */ public void onProgramEnd() { m_prefs.putInt(PREF_SORT, m_tableModel.getSortingColumn()); m_prefs.putInt(PREF_SORT_DIR, m_tableModel.getSortingDir()); PreferencesTool.putIntArray(m_prefs, PREF_WIDTHS, m_columnWidths); PreferencesTool.putIntArray(m_prefs, PREF_COLUMNS, m_columns); } /** * Hook the card context menu to given JComponent. Attaches a MouseListener * to the given component which checks for right mouse button clicks and * shows the context menu. */ public void hookCardContextMenu(JComponent comp) { comp.addMouseListener(new MouseAdapter() { public void mouseClicked(MouseEvent e) { if (SwingUtilities.isRightMouseButton(e)) { int row = rowAtPoint(e.getPoint()); if (!selectionModel.isSelectedIndex(row)) { selectionModel.setSelectionInterval(row,row); } m_cardMenu.show(e.getComponent(), e.getX(), e.getY()); } } }); } /* (non-Javadoc) * @see javax.swing.JTable#prepareRenderer */ public Component prepareRenderer(TableCellRenderer renderer, int row, int column) { Component cell = super.prepareRenderer(renderer, row, column); Color background = (row % 2 == 0) ? m_highlight : getBackground(); if (isCellSelected(row, column)) background = getSelectionBackground(); cell.setBackground(background); return cell; } /** * Return the value for given model index column in given card model. * * @param card the card from which one column value is returned. * @param modelIndex the index in model space (is'nt affected by the current * visual shown card columns or the way they are ordered in the table). * @return the value at given model index for given card. */ private static Object getValue(Card card, int modelIndex) { switch (modelIndex) { case COLUMN_FRONTSIDE: return card.getFrontSide().getText().getUnformatted().replace('\n', ' '); case COLUMN_BACKSIDE: return card.getBackSide().getText().getUnformatted().replace('\n', ' '); case COLUMN_DECK: return new Integer(card.getLevel()); case COLUMN_CATEGORY: return card.getCategory().getName(); case COLUMN_PATH: return card.getCategory().getPath(); case COLUMN_CREATED: return card.getDateCreated(); case COLUMN_MODIFIED: return card.getDateModified(); case COLUMN_TESTED: return card.getDateTested(); case COLUMN_EXPIRES: return card.getDateExpired(); case COLUMN_RATIO: return new Integer(card.getPassRatio()); default: return "-"; // this should never be reached //$NON-NLS-1$ } } private void columnSelectionChanged() { ArrayList<Integer> columns = new ArrayList<Integer>(); for (int i = 0; i < m_columns.length; i++) { columns.add(m_columns[i]); } for (int i = 0; i < COLUMN_NAMES.length; i++) { if (m_checkBoxItems[i].isSelected() && !columns.contains(i)) { columns.add(Math.min(i, columns.size()), i); } else if (!m_checkBoxItems[i].isSelected() && columns.contains(i)) { columns.remove(new Integer(i)); } } int[] columnArray = new int[columns.size()]; int idx = 0; for (int i : columns) { columnArray[idx++] = i; } setColumns(columnArray); } private void headerClicked(MouseEvent e) { if (SwingUtilities.isLeftMouseButton(e)) { JTableHeader h = (JTableHeader)e.getSource(); TableColumnModel columnModel = h.getColumnModel(); int viewColumn = columnModel.getColumnIndexAtX(e.getX()); int column = columnModel.getColumn(viewColumn).getModelIndex(); if (column != -1) { // if column already sorting invert sort direction if (column == m_tableModel.getSortingColumn()) { int dir = m_tableModel.getSortingDir(); m_tableModel.setSorting(column, dir == ViewModel.ASCENDING ? ViewModel.DESCENDING : ViewModel.ASCENDING); } // if new sorting column selected else { m_tableModel.setSorting(column, ViewModel.ASCENDING); } h.repaint(); } } else if (SwingUtilities.isRightMouseButton(e)) { // first set all checkbox menu items to not selected.. for (int i = 0; i < m_checkBoxItems.length; i++) { m_checkBoxItems[i].setSelected(false); } // ..now we only select those given as new columns for (int i = 0; i < m_columns.length; i++) { m_checkBoxItems[m_columns[i]].setSelected(true); } m_headerMenu.show(e.getComponent(), e.getX(), e.getY()); } } /** * Load table state from disk. */ private void loadFromPreferences() { // we need to check the result because there might be stored values // from older project versions that might have become invalid. m_columnWidths = PreferencesTool.getIntArray(m_prefs, PREF_WIDTHS); if (m_columnWidths == null || m_columnWidths.length != COLUMN_NAMES.length) { m_columnWidths = new int[COLUMN_NAMES.length]; } // set columns int[] columns = PreferencesTool.getIntArray(m_prefs, PREF_COLUMNS); if (columns == null || columns.length <= 0 || columns.length >= COLUMN_NAMES.length) { setColumns(m_defaultColumns); } else { setColumns(columns); } // set sorting int sortBy = m_prefs.getInt(PREF_SORT, COLUMN_EXPIRES); int sortDir = m_prefs.getInt(PREF_SORT_DIR, ViewModel.ASCENDING); if (sortBy < 0 || sortBy > COLUMN_NAMES.length - 1) { m_tableModel.setSorting(COLUMN_EXPIRES, ViewModel.ASCENDING); } else { m_tableModel.setSorting(sortBy, sortDir); } } private void updateSelectedCardCountStatusBar() { if (m_statusBar != null) { if (getSelectedRowCount() >= 2) { m_statusBar.setLeftText( Localization.get("CardTable.SELECTED_CARDS")+ ": " + //$NON-NLS-1$ //$NON-NLS-2$ getSelectedRowCount()); } else { m_statusBar.setLeftText(""); //$NON-NLS-1$ } } } private void updateCardCountStatusBar() { if (m_statusBar != null) { m_statusBar.setCards(m_tableModel.getCards()); } } private void buildHeaderMenu() { for (int i = 0; i < COLUMN_NAMES.length; i++) { m_checkBoxItems[i] = new JCheckBoxMenuItem(COLUMN_NAMES[i]); m_checkBoxItems[i].addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent e) { columnSelectionChanged(); } }); m_headerMenu.add(m_checkBoxItems[i]); } } private JPopupMenu buildCardContextMenu() { JPopupMenu menu = new JPopupMenu(); menu.add(new LearnAction(this)); menu.addSeparator(); menu.add(new AddCardAction(this)); menu.add(new EditCardAction(this)); menu.add(new ResetCardAction(this)); menu.add(new RemoveAction(this)); menu.addSeparator(); menu.add(new CopyAction(this)); menu.add(new CutAction(this)); menu.add(new PasteAction(this)); return menu; } }