/* * This file is part of muCommander, http://www.mucommander.com * Copyright (C) 2002-2016 Maxence Bernard * * muCommander 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. * * muCommander 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 com.mucommander.ui.dialog.pref.general; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Component; import java.awt.Dimension; import java.awt.Font; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Insets; import java.awt.Point; import java.awt.Rectangle; import java.awt.event.FocusEvent; import java.awt.event.FocusListener; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.awt.image.BufferedImage; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import javax.swing.BorderFactory; import javax.swing.DefaultCellEditor; import javax.swing.ImageIcon; import javax.swing.JTable; import javax.swing.JTextField; import javax.swing.JViewport; import javax.swing.KeyStroke; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import javax.swing.table.DefaultTableModel; import javax.swing.table.TableCellEditor; import javax.swing.table.TableCellRenderer; import javax.swing.table.TableModel; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.mucommander.commons.runtime.OsFamily; import com.mucommander.commons.runtime.OsVersion; import com.mucommander.commons.util.Pair; import com.mucommander.text.Translator; import com.mucommander.ui.action.ActionDescriptor; import com.mucommander.ui.action.ActionKeymap; import com.mucommander.ui.action.ActionManager; import com.mucommander.ui.action.ActionProperties; import com.mucommander.ui.dialog.pref.component.PrefTable; import com.mucommander.ui.dialog.pref.general.ShortcutsPanel.TooltipBar; import com.mucommander.ui.icon.IconManager; import com.mucommander.ui.main.table.CellLabel; import com.mucommander.ui.table.CenteredTableHeaderRenderer; import com.mucommander.ui.text.KeyStrokeUtils; import com.mucommander.ui.theme.ColorChangedEvent; import com.mucommander.ui.theme.FontChangedEvent; import com.mucommander.ui.theme.Theme; import com.mucommander.ui.theme.ThemeCache; import com.mucommander.ui.theme.ThemeListener; /** * This class is the table in which the actions and their shortcuts are * present in the ShortcutsPanel. * * @author Arik Hadas, Johann Schmitz (johann@j-schmitz.net) */ public class ShortcutsTable extends PrefTable implements KeyListener, ListSelectionListener, FocusListener { private static final Logger LOGGER = LoggerFactory.getLogger(ShortcutsTable.class); /** Base width and height of icons for a scale factor of 1 */ private final static int BASE_ICON_DIMENSION = 16; /** Transparent icon used to align non-locked themes with the others. */ private static ImageIcon transparentIcon = new ImageIcon(new BufferedImage(BASE_ICON_DIMENSION, BASE_ICON_DIMENSION, BufferedImage.TYPE_INT_ARGB)); /** Private object used to indicate that a delete operation was made */ public static final Object DELETE = new Object(); private ShortcutsTableData data; /** Comparator of actions according to their labels */ private static final Comparator<String> ACTIONS_COMPARATOR = new Comparator<String>() { public int compare(String id1, String id2) { String label1 = ActionProperties.getActionLabel(id1); if (label1 == null) return 1; String label2 = ActionProperties.getActionLabel(id2); if (label2 == null) return -1; return label1.compareTo(label2); } }; /** Last selected row in the table */ private int lastSelectedRow = -1; /** The bar below the table in which messages can be displayed */ private TooltipBar tooltipBar; /** Number of mouse clicks required to enter cell's editing state */ private static final int NUM_OF_CLICKS_TO_ENTER_EDITING_STATE = 2; /** Column indexes */ public static final int ACTION_DESCRIPTION_COLUMN_INDEX = 0; public static final int ACCELERATOR_COLUMN_INDEX = 1; public static final int ALTERNATE_ACCELERATOR_COLUMN_INDEX = 2; /** Number of columns in the table */ private static final int NUM_OF_COLUMNS = 3; /** After the following time (msec) that cell is being in editing state * and no pressing was made, the editing state is canceled */ private static final int CELL_EDITING_STATE_PERIOD = 3000; /** Thread that cancel cell's editing state after CELL_EDITING_STATE_PERIOD time */ private CancelEditingStateThread cancelEditingStateThread; private ShortcutsTableCellRenderer cellRenderer; public ShortcutsTable(TooltipBar tooltipBar) { super(); this.tooltipBar = tooltipBar; setModel(new KeymapTableModel(data = new ShortcutsTableData())); cellRenderer = new ShortcutsTableCellRenderer(); setShowGrid(false); setIntercellSpacing(new Dimension(0,0)); setRowHeight(Math.max(getRowHeight(), BASE_ICON_DIMENSION + 2 * CellLabel.CELL_BORDER_HEIGHT)); getTableHeader().setReorderingAllowed(false); setRowSelectionAllowed(false); setAutoCreateColumnsFromModel(false); setCellSelectionEnabled(false); setColumnSelectionAllowed(false); setDragEnabled(false); if (!usesTableHeaderRenderingProperties()) { CenteredTableHeaderRenderer renderer = new CenteredTableHeaderRenderer(); getColumnModel().getColumn(ACTION_DESCRIPTION_COLUMN_INDEX).setHeaderRenderer(renderer); getColumnModel().getColumn(ACCELERATOR_COLUMN_INDEX).setHeaderRenderer(renderer); getColumnModel().getColumn(ALTERNATE_ACCELERATOR_COLUMN_INDEX).setHeaderRenderer(renderer); } putClientProperty("terminateEditOnFocusLost", Boolean.TRUE); addKeyListener(this); addFocusListener(this); getSelectionModel().addListSelectionListener(this); getColumnModel().getSelectionModel().addListSelectionListener(this); } /** * Paints a dotted border of the specified width, height and {@link Color}, and using the given {@link Graphics} * object. * * @param g Graphics object to use for painting * @param width border width * @param height border height * @param color border color */ private static void paintDottedBorder(Graphics g, int width, int height, Color color) { Graphics2D g2 = (Graphics2D) g; g2.setStroke(new BasicStroke(1.0f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_MITER, 2.0f, new float[]{2.0f}, 0)); g2.setColor(color); g2.drawLine(0, 0, width, 0); g2.drawLine(0, height - 1, width, height - 1); g2.drawLine(0, 0, 0, height - 1); g2.drawLine(width-1, 0, width-1, height - 1); } private static boolean usesTableHeaderRenderingProperties() { return OsFamily.MAC_OS_X.isCurrent() && OsVersion.MAC_OS_X_10_5.isCurrentOrHigher(); } /** * Assumes table is contained in a JScrollPane. Scrolls the * cell (rowIndex, vColIndex) so that it is visible within the viewport. */ public void scrollToVisible(int rowIndex, int vColIndex) { if (!(getParent() instanceof JViewport)) return; JViewport viewport = (JViewport) getParent(); // This rectangle is relative to the table where the // northwest corner of cell (0,0) is always (0,0). Rectangle rect = getCellRect(rowIndex, vColIndex, true); // The location of the viewport relative to the table Point pt = viewport.getViewPosition(); // Translate the cell location so that it is relative // to the view, assuming the northwest corner of the // view is (0,0) rect.setLocation(rect.x-pt.x, rect.y-pt.y); // Scroll the area into view viewport.scrollRectToVisible(rect); } /** * Create thread that will cancel the editing state of the given TableCellEditor * after CELL_EDITING_STATE_PERIOD time in which with no pressing was made. */ public void createCancelEditingStateThread(TableCellEditor cellEditor) { if (cancelEditingStateThread != null) cancelEditingStateThread.neutralize(); (cancelEditingStateThread = new CancelEditingStateThread(cellEditor)).start(); } @Override public TableCellRenderer getCellRenderer(int row, int column) { return cellRenderer; } @Override public void valueChanged(ListSelectionEvent e) { super.valueChanged(e); // Selection might be changed, update tooltip int selectetRow = getSelectedRow(); if (selectetRow == -1) // no row is selected tooltipBar.showDefaultMessage(); else tooltipBar.showActionTooltip(data.getCurrentTooltip()); } public void updateModel(ActionFilter filter) { data.filter(filter); } /** * Override this method so that calls for SetModel function outside this class * won't get to setModel(KeymapTableModel model) function. */ @Override public void setModel(TableModel model) { super.setModel(model); } public boolean hasChanged() { return data.hasChanged(); } @Override public TableCellEditor getCellEditor(int row, int column) { return new KeyStrokeCellEditor(new RecordingKeyStrokeField((KeyStroke) getValueAt(row, column))); } /** * This method updates ActionKeymap with the modified shortcuts. */ public void commitChanges() { data.submitChanges(); } public void restoreDefaults() { data.restoreDefaultAccelerators(); } /////////////////////////// // FocusListener methods // /////////////////////////// public void focusGained(FocusEvent e) { int currentSelectedRow = getSelectedRow(); if (lastSelectedRow != currentSelectedRow) tooltipBar.showActionTooltip(data.getCurrentTooltip()); lastSelectedRow = currentSelectedRow; } public void focusLost(FocusEvent e) { } ///////////////////////////// //// KeyListener methods //// ///////////////////////////// public void keyPressed(KeyEvent e) { int keyCode = e.getKeyCode(); if (keyCode == KeyEvent.VK_ENTER) { if (editCellAt(getSelectedRow(), getSelectedColumn())) getEditorComponent().requestFocusInWindow(); e.consume(); } else if (keyCode == KeyEvent.VK_DELETE || keyCode == KeyEvent.VK_BACK_SPACE) { setValueAt(DELETE, getSelectedRow(), getSelectedColumn()); repaint(); e.consume(); } else if (keyCode != KeyEvent.VK_LEFT && keyCode != KeyEvent.VK_RIGHT && keyCode != KeyEvent.VK_UP && keyCode != KeyEvent.VK_DOWN && keyCode != KeyEvent.VK_HOME && keyCode != KeyEvent.VK_END && keyCode != KeyEvent.VK_F2 && keyCode != KeyEvent.VK_ESCAPE) e.consume(); } public void keyReleased(KeyEvent e) {} public void keyTyped(KeyEvent e) {} public static abstract class ActionFilter { public abstract boolean accept(String actionId); } /** * Helper Classes */ private class KeyStrokeCellEditor extends DefaultCellEditor implements TableCellEditor { RecordingKeyStrokeField rec; public KeyStrokeCellEditor(RecordingKeyStrokeField rec) { super(rec); this.rec = rec; rec.setSelectionColor(rec.getBackground()); rec.setSelectedTextColor(rec.getForeground()); rec.getDocument().addDocumentListener(new DocumentListener() { public void insertUpdate(DocumentEvent e) { // quit editing state after text is written to the text field. stopCellEditing(); } public void changedUpdate(DocumentEvent e) {} public void removeUpdate(DocumentEvent e) {} }); setClickCountToStart(NUM_OF_CLICKS_TO_ENTER_EDITING_STATE); createCancelEditingStateThread(this); } @Override public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int col) { return rec; } @Override public Object getCellEditorValue() { return rec.getLastKeyStroke(); } } private class CancelEditingStateThread extends Thread { private boolean stopped = false; private TableCellEditor cellEditor; public CancelEditingStateThread(TableCellEditor cellEditor) { this.cellEditor = cellEditor; } public void neutralize() { stopped = true; } @Override public void run() { try { Thread.sleep(CELL_EDITING_STATE_PERIOD); } catch (InterruptedException e) {} if (!stopped && cellEditor != null) cellEditor.stopCellEditing(); } } private class KeymapTableModel extends DefaultTableModel { private ShortcutsTableData tableData = null; private KeymapTableModel(ShortcutsTableData data) { super(data.getTableData(), new String[] {Translator.get("shortcuts_table.action_description"), Translator.get("shortcuts_table.shortcut"), Translator.get("shortcuts_table.alternate_shortcut")}); this.tableData = data; } @Override public boolean isCellEditable(int row, int column) { switch(column) { case ACTION_DESCRIPTION_COLUMN_INDEX: return false; case ACCELERATOR_COLUMN_INDEX: case ALTERNATE_ACCELERATOR_COLUMN_INDEX: return true; default: return false; } } @Override public Object getValueAt(int row, int column) { return tableData.getTableData(row, column); } @Override public void setValueAt(Object value, int row, int column) { // if no keystroke was pressed if (value == null) return; // if the user pressed a keystroke that is used to indicate a delete operation should be made else if (value == DELETE) value = null; KeyStroke typedKeyStroke = (KeyStroke) value; switch(column){ case ACCELERATOR_COLUMN_INDEX: tableData.setAccelerator(typedKeyStroke, row); break; case ALTERNATE_ACCELERATOR_COLUMN_INDEX: tableData.setAlternativeAccelerator(typedKeyStroke, row); break; default: LOGGER.debug("Unexpected column index: " + column); } fireTableCellUpdated(row, column); LOGGER.trace("Value: " + value + ", row: " + row + ", col: " + column); } } private class RecordingKeyStrokeField extends JTextField implements KeyListener { // The last KeyStroke that was entered to the field. // Before any keystroke is entered, it contains the keystroke appearing in the cell before entering the editing state. private KeyStroke lastKeyStroke; public RecordingKeyStrokeField(KeyStroke currentKeyStroke) { super(Translator.get("shortcuts_table.type_in_a_shortcut")); lastKeyStroke = currentKeyStroke; setBorder(BorderFactory.createEmptyBorder()); setHorizontalAlignment(JTextField.CENTER); setEditable(false); setBackground(ThemeCache.backgroundColors[ThemeCache.ACTIVE][ThemeCache.SELECTED]); setForeground(ThemeCache.foregroundColors[ThemeCache.ACTIVE][ThemeCache.SELECTED][ThemeCache.PLAIN_FILE]); addKeyListener(this); // It is required to disable the traversal keys in order to support keys combination that include the TAB key setFocusTraversalKeysEnabled(false); } /** * * @return the last KeyStroke the user entered to the field. */ public KeyStroke getLastKeyStroke() { return lastKeyStroke; } //////////////////////// // Overridden methods // //////////////////////// @Override protected void paintBorder(Graphics g) { paintDottedBorder(g, getWidth(), getHeight(), ThemeCache.backgroundColors[ThemeCache.ACTIVE][ThemeCache.NORMAL]); } ///////////////////////////// //// KeyListener methods //// ///////////////////////////// public void keyPressed(KeyEvent keyEvent) { LOGGER.trace("keyModifiers="+keyEvent.getModifiers()+" keyCode="+keyEvent.getKeyCode()); int keyCode = keyEvent.getKeyCode(); if(keyCode==KeyEvent.VK_SHIFT || keyCode==KeyEvent.VK_CONTROL || keyCode==KeyEvent.VK_ALT || keyCode==KeyEvent.VK_META) return; KeyStroke pressedKeyStroke = KeyStroke.getKeyStrokeForEvent(keyEvent); if (pressedKeyStroke.equals(lastKeyStroke)) { TableCellEditor activeCellEditor = getCellEditor(); if (activeCellEditor!= null) activeCellEditor.stopCellEditing(); } else { String actionId; if ((actionId = data.contains(pressedKeyStroke)) != null) { String errorMessage = "The shortcut [" + KeyStrokeUtils.getKeyStrokeDisplayableRepresentation(pressedKeyStroke) + "] is already assigned to '" + ActionProperties.getActionDescription(actionId) + "'"; tooltipBar.showErrorMessage(errorMessage); createCancelEditingStateThread(getCellEditor()); } else { lastKeyStroke = pressedKeyStroke; setText(KeyStrokeUtils.getKeyStrokeDisplayableRepresentation(lastKeyStroke)); } } keyEvent.consume(); } public void keyReleased(KeyEvent e) {e.consume();} public void keyTyped(KeyEvent e) {e.consume();} } private class ShortcutsTableData { private Object[][] data; private String[] actionIds; private String[] descriptions; private final Integer description = 0; private final Integer accelerator = 1; private final Integer alt_accelerator = 2; private final Integer tooltips = 3; private List<String> allActionIds; private HashMap<String, HashMap<Integer, Object>> db; public ShortcutsTableData() { allActionIds = new ArrayList<String>(); Iterator<String> iterator = ActionManager.getActionIds(); while(iterator.hasNext()) allActionIds.add(iterator.next()); Collections.sort(allActionIds, ACTIONS_COMPARATOR); final int nbActions = allActionIds.size(); db = new HashMap<String, HashMap<Integer, Object>>(nbActions); int nbRows = allActionIds.size(); data = new Object[nbRows][NUM_OF_COLUMNS]; for(String actionId : allActionIds) { ActionDescriptor actionDescriptor = ActionProperties.getActionDescriptor(actionId); HashMap<Integer, Object> actionProperties = new HashMap<Integer, Object>(); ImageIcon actionIcon = actionDescriptor.getIcon(); if (actionIcon == null) actionIcon = transparentIcon; String actionLabel = actionDescriptor.getLabel(); /* 0 -> action's icon & name pair */ actionProperties.put(description, new Pair<ImageIcon, String>(IconManager.getPaddedIcon(actionIcon, new Insets(0, 4, 0, 4)), actionLabel)); /* 1 -> action's accelerator */ actionProperties.put(accelerator, ActionKeymap.getAccelerator(actionId)); /* 2 -> action's alternate accelerator */ actionProperties.put(alt_accelerator, ActionKeymap.getAlternateAccelerator(actionId)); /* 3 -> action's description */ actionProperties.put(tooltips, actionDescriptor.getDescription()); db.put(actionId, actionProperties); } } public void filter(ActionFilter filter) { List<String> filteredActionIds = filter(allActionIds, filter); // Build the table data int nbRows = filteredActionIds.size(); actionIds = new String[nbRows]; descriptions = new String[nbRows]; data = new Object[nbRows][NUM_OF_COLUMNS]; for (int i = 0; i < nbRows; ++i) { String actionId = filteredActionIds.get(i); actionIds[i] = actionId; ActionDescriptor actionDescriptor = ActionProperties.getActionDescriptor(actionId); data[i][ACTION_DESCRIPTION_COLUMN_INDEX] = db.get(actionId).get(this.description); KeyStroke accelerator = (KeyStroke) db.get(actionId).get(this.accelerator); setAccelerator(accelerator, i); KeyStroke alternativeAccelerator = (KeyStroke) db.get(actionId).get(this.alt_accelerator); setAlternativeAccelerator(alternativeAccelerator, i); descriptions[i] = actionDescriptor.getDescription(); } ShortcutsTable.this.clearSelection(); ((DefaultTableModel) getModel()).setRowCount(data.length); ShortcutsTable.this.repaint(); ShortcutsTable.this.scrollToVisible(0, 0); } public Object[][] getTableData() { return data; } public Object getTableData(int row, int col) { return data[row][col]; } public String getCurrentTooltip() { return descriptions[getSelectedRow()]; } public String getActionId(int row) { return actionIds[row]; } public boolean hasChanged() { for (String actionId : db.keySet()) { HashMap<Integer, Object> actionProperties = db.get(actionId); if (!equals(actionProperties.get(this.accelerator), ActionKeymap.getAccelerator(actionId)) || !equals(actionProperties.get(this.alt_accelerator), ActionKeymap.getAlternateAccelerator(actionId))) return true; } return false; } public void restoreDefaultAccelerators() { for (String actionId : allActionIds) { (db.get(actionId)).put(this.accelerator, ActionProperties.getDefaultAccelerator(actionId)); (db.get(actionId)).put(this.alt_accelerator, ActionProperties.getDefaultAlternativeAccelerator(actionId)); } int nbRows = actionIds.length; for (int i=0; i<nbRows; ++i) { data[i][ACCELERATOR_COLUMN_INDEX] = db.get(actionIds[i]).get(this.accelerator); data[i][ALTERNATE_ACCELERATOR_COLUMN_INDEX] = db.get(actionIds[i]).get(this.alt_accelerator); } ((DefaultTableModel) getModel()).fireTableDataChanged(); } public void submitChanges() { for (String actionId : db.keySet()) { HashMap<Integer, Object> actionProperties = db.get(actionId); KeyStroke accelerator = (KeyStroke) actionProperties.get(this.accelerator); KeyStroke alternateAccelerator = (KeyStroke) actionProperties.get(this.alt_accelerator); // If action's accelerators differ from its saved accelerators, register them. if (!equals(accelerator, ActionKeymap.getAccelerator(actionId)) || !equals(alternateAccelerator, ActionKeymap.getAlternateAccelerator(actionId))) ActionKeymap.changeActionAccelerators(actionId, accelerator, alternateAccelerator); } } public String contains(KeyStroke accelerator) { if (accelerator != null) { for (String actionId : db.keySet()) { if (accelerator.equals(db.get(actionId).get(this.accelerator)) || accelerator.equals(db.get(actionId).get(this.alt_accelerator))) return actionId; } } return null; } private void setAccelerator(KeyStroke accelerator, int row) { data[row][ACCELERATOR_COLUMN_INDEX] = accelerator; db.get(getActionId(row)).put(this.accelerator, accelerator); } private void setAlternativeAccelerator(KeyStroke altAccelerator, int row) { data[row][ALTERNATE_ACCELERATOR_COLUMN_INDEX] = altAccelerator; db.get(getActionId(row)).put(this.alt_accelerator, altAccelerator); } private List<String> filter(List<String> actionIds, ActionFilter filter) { List<String> filteredActionsList = new LinkedList<String>(); for (String actionId : actionIds) { // Discard actions that are parameterized, and those that are rejected by the filter if (!ActionProperties.getActionDescriptor(actionId).isParameterized() && filter.accept(actionId)) filteredActionsList.add(actionId); } return filteredActionsList; } private boolean equals(Object obj1, Object obj2) { if (obj1 == null) return obj2 == null; return obj1.equals(obj2); } } private class ShortcutsTableCellRenderer implements TableCellRenderer, ThemeListener { /** Custom JLabel that render specific column cells */ private DotBorderedCellLabel[] cellLabels = new DotBorderedCellLabel[NUM_OF_COLUMNS]; public ShortcutsTableCellRenderer() { for(int i=0; i<NUM_OF_COLUMNS; ++i) cellLabels[i] = new DotBorderedCellLabel(); // Set labels' font. setCellLabelsFont(ThemeCache.tableFont); cellLabels[ACTION_DESCRIPTION_COLUMN_INDEX].setHorizontalAlignment(CellLabel.LEFT); cellLabels[ACCELERATOR_COLUMN_INDEX].setHorizontalAlignment(CellLabel.CENTER); cellLabels[ALTERNATE_ACCELERATOR_COLUMN_INDEX].setHorizontalAlignment(CellLabel.CENTER); // Listens to certain configuration variables ThemeCache.addThemeListener(this); } /** * Sets CellLabels' font to the current one. */ private void setCellLabelsFont(Font newFont) { // Set custom font for(int i=0; i<NUM_OF_COLUMNS; ++i) cellLabels[i].setFont(newFont); } public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int rowIndex, int vColIndex) { DotBorderedCellLabel label; int columnId; columnId = convertColumnIndexToModel(vColIndex); label = cellLabels[columnId]; // action's icon column: return ImageIcon instance if(columnId == ACTION_DESCRIPTION_COLUMN_INDEX) { Pair<ImageIcon, String> description = (Pair<ImageIcon, String>) value; label.setIcon(description.first); label.setText(description.second); // set cell's foreground color label.setForeground(ThemeCache.foregroundColors[ThemeCache.ACTIVE][ThemeCache.NORMAL][ThemeCache.PLAIN_FILE]); } // Any other column else { final KeyStroke key = (KeyStroke) value; String text = key == null ? "" : KeyStrokeUtils.getKeyStrokeDisplayableRepresentation(key); // If component's preferred width is bigger than column width then the component is not entirely // visible so we set a tooltip text that will display the whole text when mouse is over the component if (table.getColumnModel().getColumn(vColIndex).getWidth() < label.getPreferredSize().getWidth()) label.setToolTipText(text); // Have to set it to null otherwise the defaultRender sets the tooltip text to the last one specified else label.setToolTipText(null); // Set label's text label.setText(text); // set cell's foreground color if (key != null) { boolean customized; switch (columnId) { case ACCELERATOR_COLUMN_INDEX: customized = !key.equals(ActionProperties.getDefaultAccelerator(data.getActionId(rowIndex))); break; case ALTERNATE_ACCELERATOR_COLUMN_INDEX: customized = !key.equals(ActionProperties.getDefaultAlternativeAccelerator(data.getActionId(rowIndex))); break; default: customized = false; } label.setForeground(ThemeCache.foregroundColors[ThemeCache.ACTIVE][ThemeCache.NORMAL][customized ? ThemeCache.PLAIN_FILE : ThemeCache.HIDDEN_FILE]); } } // set outline for the focused cell label.setOutline(hasFocus ? ThemeCache.backgroundColors[ThemeCache.ACTIVE][ThemeCache.SELECTED] : null); // set cell's background color label.setBackground(ThemeCache.backgroundColors[ThemeCache.ACTIVE][rowIndex % 2 == 0 ? ThemeCache.NORMAL : ThemeCache.ALTERNATE]); return label; } // - Theme listening ------------------------------------------------------------- // ------------------------------------------------------------------------------- /** * Receives theme color changes notifications. */ public void colorChanged(ColorChangedEvent event) { } /** * Receives theme font changes notifications. */ public void fontChanged(FontChangedEvent event) { if(event.getFontId() == Theme.FILE_TABLE_FONT) { setCellLabelsFont(ThemeCache.tableFont); } } } /** * CellLabel with a dotted outline. */ private class DotBorderedCellLabel extends CellLabel { @Override protected void paintOutline(Graphics g) { paintDottedBorder(g, getWidth(), getHeight(), outlineColor); } } }