) this::getFileChooser);
+ detailsTable.putClientProperty("JTable.fileNameColumnIndex", COLUMN_FILENAME);
+
+
+ Font font = list.getFont();
+ detailsTable.setFont(font);
+
+ TableCellRenderer headerRenderer =
+ new AlignableTableHeaderRenderer(detailsTable.getTableHeader().getDefaultRenderer());
+ detailsTable.getTableHeader().setDefaultRenderer(headerRenderer);
+ TableCellRenderer cellRenderer = new DetailsTableCellRenderer(chooser);
+ detailsTable.setDefaultRenderer(Object.class, cellRenderer);
+
+ if (getFileChooser().isMultiSelectionEnabled()) {
+ detailsTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
+ } else {
+ detailsTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+ }
+
+ detailsTable.addMouseListener(getMouseHandler());
+
+ // 4835633 : tell BasicTableUI that this is a file list
+ detailsTable.putClientProperty("Table.isFileList", Boolean.TRUE);
+
+ if (listViewWindowsStyle) {
+ detailsTable.addFocusListener(repaintListener);
+ }
+
+ // TAB/SHIFT-TAB should transfer focus and ENTER should select an item.
+ // We don't want them to navigate within the table
+ ActionMap am = SwingUtilities.getUIActionMap(detailsTable);
+ am.remove("selectNextRowCell");
+ am.remove("selectPreviousRowCell");
+ am.remove("selectNextColumnCell");
+ am.remove("selectPreviousColumnCell");
+ detailsTable.setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS,
+ null);
+ detailsTable.setFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS,
+ null);
+
+ OverlayScrollPane overlayScrollPane = new OverlayScrollPane(detailsTable);
+ JScrollPane scrollPane = overlayScrollPane.getScrollPane();
+ scrollPane.setComponentOrientation(chooser.getComponentOrientation());
+ LookAndFeel.installColors(scrollPane.getViewport(), "Table.background", "Table.foreground");
+
+ // Adjust width of first column so the table fills the viewport when
+ // first displayed (temporary listener).
+ scrollPane.addComponentListener(new ComponentAdapter() {
+ public void componentResized(final ComponentEvent e) {
+ JScrollPane sp = (JScrollPane) e.getComponent();
+ fixNameColumnWidth(sp.getViewport().getSize().width);
+ sp.removeComponentListener(this);
+ }
+ });
+
+ detailsTable.setForeground(list.getForeground());
+ detailsTable.setBackground(list.getBackground());
+
+ if (listViewBorder != null) {
+ scrollPane.setBorder(listViewBorder);
+ }
+ p.add(overlayScrollPane, BorderLayout.CENTER);
+
+ detailsTableModel.fireTableStructureChanged();
+
+ detailsTable.putClientProperty(AccessibleContext.ACCESSIBLE_NAME_PROPERTY, filesDetailsAccessibleName);
+
+ return p;
+ }
+
+ protected void cancelEdit() {
+ if (editFile != null) {
+ editFile = null;
+ list.remove(editCell);
+ list.putClientProperty("JList.isEditing", false);
+ repaint();
+ } else if (detailsTable != null && detailsTable.isEditing()) {
+ detailsTable.getCellEditor().cancelCellEditing();
+ }
+ }
+
+ protected void editFileName(final int index) {
+ JFileChooser chooser = getFileChooser();
+ File currentDirectory = chooser.getCurrentDirectory();
+
+ if (readOnly || !canWrite(currentDirectory, chooser)) {
+ return;
+ }
+
+ ensureIndexIsVisible(index);
+ switch (viewType) {
+ case VIEWTYPE_LIST:
+ editFile = (File) getModel().getElementAt(getRowSorter().convertRowIndexToModel(index));
+ Rectangle r = list.getCellBounds(index, index);
+ if (editCell == null) {
+ editCell = new JTextField();
+ editCell.setName("Tree.cellEditor");
+ editCell.addActionListener(new EditActionListener());
+ editCell.addFocusListener(editorFocusListener);
+ editCell.setNextFocusableComponent(list);
+ }
+ list.add(editCell);
+ editCell.setText(chooser.getName(editFile));
+ ComponentOrientation orientation = list.getComponentOrientation();
+ editCell.setComponentOrientation(orientation);
+
+ Icon icon = chooser.getIcon(editFile);
+
+ // PENDING - grab padding (4) below from defaults table.
+ int editX = icon == null ? 20 : icon.getIconWidth() + 4;
+
+ int gap = 0;
+ var renderer = list.getCellRenderer();
+ if (renderer instanceof JLabel) {
+ gap = ((JLabel) renderer).getIconTextGap() - 1;
+ }
+ if (orientation.isLeftToRight()) {
+ editCell.setBounds(editX + r.x + gap, r.y, r.width - editX - gap, r.height);
+ } else {
+ editCell.setBounds(r.x, r.y, r.width - editX - gap, r.height);
+ }
+ list.putClientProperty("JList.isEditing", true);
+ editCell.requestFocus();
+ editCell.selectAll();
+ break;
+
+ case VIEWTYPE_DETAILS:
+ detailsTable.editCellAt(index, COLUMN_FILENAME);
+ break;
+ }
+ }
+
+ public JPopupMenu getComponentPopupMenu() {
+ JPopupMenu popupMenu = getFileChooser().getComponentPopupMenu();
+ if (popupMenu != null) {
+ return popupMenu;
+ }
+
+ JMenu viewMenu = getViewMenu();
+ if (contextMenu == null) {
+ contextMenu = new JPopupMenu();
+ if (viewMenu != null) {
+ contextMenu.add(viewMenu);
+ if (listViewWindowsStyle) {
+ contextMenu.addSeparator();
+ }
+ }
+ ActionMap actionMap = getActionMap();
+ Action refreshAction = actionMap.get(ACTION_REFRESH);
+ Action newFolderAction = actionMap.get(ACTION_NEW_FOLDER);
+ Action renameAction = actionMap.get(ACTION_EDIT_FILE_NAME);
+ if (refreshAction != null) {
+ contextMenu.add(refreshAction);
+ }
+ if (renameAction != null) {
+ var menuItem = new JMenuItem(renameAction);
+ menuItem.setText("Rename");
+ contextMenu.add(menuItem);
+ }
+ if (newFolderAction != null) {
+ contextMenu.add(newFolderAction);
+ }
+ }
+ if (viewMenu != null) {
+ viewMenu.getPopupMenu().setInvoker(viewMenu);
+ }
+ return contextMenu;
+ }
+
+ @Override
+ protected Handler getMouseHandler() {
+ if (handler == null) {
+ handler = new DarkHandler();
+ }
+ return handler;
+ }
+
+ protected class DarkHandler extends Handler {
+
+ @Override
+ public void mouseClicked(MouseEvent evt) {
+ JComponent source = (JComponent) evt.getSource();
+
+ int index;
+ if (source instanceof JList) {
+ index = list.locationToIndex(evt.getPoint());
+ } else if (source instanceof JTable) {
+ JTable table = (JTable) source;
+ Point p = evt.getPoint();
+ index = table.rowAtPoint(p);
+
+ boolean pointOutsidePrefSize = SwingUtilities2.pointOutsidePrefSize(table, index,
+ table.columnAtPoint(p), p);
+
+ if (pointOutsidePrefSize && !fullRowSelection) {
+ return;
+ }
+
+ // Translate point from table to list
+ if (index >= 0 && list != null && listSelectionModel.isSelectedIndex(index)) {
+
+ // Make a new event with the list as source, placing the
+ // click in the corresponding list cell.
+ Rectangle r = list.getCellBounds(index, index);
+ MouseEvent newEvent = new MouseEvent(list, evt.getID(),
+ evt.getWhen(), evt.getModifiersEx(),
+ r.x + 1, r.y + r.height / 2,
+ evt.getXOnScreen(),
+ evt.getYOnScreen(),
+ evt.getClickCount(), evt.isPopupTrigger(),
+ evt.getButton());
+ AWTAccessor.MouseEventAccessor meAccessor = AWTAccessor.getMouseEventAccessor();
+ meAccessor.setCausedByTouchEvent(newEvent,
+ meAccessor.isCausedByTouchEvent(evt));
+ evt = newEvent;
+ }
+ } else {
+ return;
+ }
+
+ if (index >= 0 && SwingUtilities.isLeftMouseButton(evt)) {
+ JFileChooser fc = getFileChooser();
+
+ // For single click, we handle editing file name
+ if (evt.getClickCount() == 1 && source instanceof JList) {
+ if ((!fc.isMultiSelectionEnabled() || fc.getSelectedFiles().length <= 1)
+ && listSelectionModel.isSelectedIndex(index)
+ && getEditIndex() == index && editFile == null
+ && DarkUIUtil.isOverText(evt, index, list)) {
+ editFileName(index);
+ } else {
+ setEditIndex(index);
+ }
+ } else if (evt.getClickCount() == 2) {
+ // on double click (open or drill down one directory) be
+ // sure to clear the edit index
+ resetEditIndex();
+ }
+ }
+
+ // Forward event to Basic
+ if (getDoubleClickListener() != null) {
+ list.putClientProperty("List.isFileList", false);
+ getDoubleClickListener().mouseClicked(evt);
+ list.putClientProperty("List.isFileList", true);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/weis/darklaf/ui/filechooser/DarkFilePaneUIBridge.java b/src/main/java/com/weis/darklaf/ui/filechooser/DarkFilePaneUIBridge.java
new file mode 100644
index 00000000..4e768bcb
--- /dev/null
+++ b/src/main/java/com/weis/darklaf/ui/filechooser/DarkFilePaneUIBridge.java
@@ -0,0 +1,2060 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2019 Jannis Weis
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+package com.weis.darklaf.ui.filechooser;
+
+import com.weis.darklaf.ui.list.DarkListCellRenderer;
+import com.weis.darklaf.ui.table.DarkTableCellEditor;
+import com.weis.darklaf.ui.table.DarkTableCellRenderer;
+import com.weis.darklaf.ui.table.TextFieldTableCellEditorBorder;
+import org.jetbrains.annotations.NotNull;
+import sun.awt.AWTAccessor;
+import sun.awt.shell.ShellFolder;
+import sun.awt.shell.ShellFolderColumnInfo;
+import sun.swing.FilePane;
+import sun.swing.SwingUtilities2;
+
+import javax.accessibility.AccessibleContext;
+import javax.swing.*;
+import javax.swing.border.Border;
+import javax.swing.event.ListDataEvent;
+import javax.swing.event.ListDataListener;
+import javax.swing.event.ListSelectionListener;
+import javax.swing.event.RowSorterEvent;
+import javax.swing.event.RowSorterListener;
+import javax.swing.event.TableModelEvent;
+import javax.swing.event.TableModelListener;
+import javax.swing.filechooser.FileSystemView;
+import javax.swing.plaf.basic.BasicDirectoryModel;
+import javax.swing.table.AbstractTableModel;
+import javax.swing.table.DefaultTableColumnModel;
+import javax.swing.table.TableCellEditor;
+import javax.swing.table.TableCellRenderer;
+import javax.swing.table.TableColumn;
+import javax.swing.table.TableColumnModel;
+import javax.swing.table.TableModel;
+import javax.swing.table.TableRowSorter;
+import javax.swing.text.Position;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ComponentAdapter;
+import java.awt.event.ComponentEvent;
+import java.awt.event.FocusAdapter;
+import java.awt.event.FocusEvent;
+import java.awt.event.FocusListener;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+import java.awt.event.KeyListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.text.DateFormat;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+import java.util.Objects;
+import java.util.concurrent.Callable;
+
+/**
+ * WARNING: This class is an implementation detail and is only
+ * public so that it can be used by two packages. You should NOT consider
+ * this public API.
+ *
+ * This component is intended to be used in a subclass of
+ * javax.swing.plaf.basic.BasicFileChooserUI. It realies heavily on the
+ * implementation of BasicFileChooserUI, and is intended to be API compatible
+ * with earlier implementations of MetalFileChooserUI and WindowsFileChooserUI.
+ *
+ * @author Leif Samuelsson
+ */
+public class DarkFilePaneUIBridge extends JPanel implements PropertyChangeListener {
+
+ // Constants for actions. These are used for the actions' ACTION_COMMAND_KEY
+ // and as keys in the action maps for FilePane and the corresponding UI classes
+
+ public static final String ACTION_APPROVE_SELECTION = "approveSelection";
+ public static final String ACTION_CANCEL = "cancelSelection";
+ public static final String ACTION_EDIT_FILE_NAME = "editFileName";
+ public static final String ACTION_REFRESH = "refresh";
+ public static final String ACTION_CHANGE_TO_PARENT_DIRECTORY = "Go Up";
+ public static final String ACTION_NEW_FOLDER = "New Folder";
+ public static final String ACTION_VIEW_LIST = "viewTypeList";
+ public static final String ACTION_VIEW_DETAILS = "viewTypeDetails";
+ // "enums" for setViewType()
+ public static final int VIEWTYPE_LIST = 0;
+ public static final int VIEWTYPE_DETAILS = 1;
+ protected static final int VIEWTYPE_COUNT = 2;
+ protected static final Cursor waitCursor =
+ Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR);
+ protected static final int COLUMN_FILENAME = 0;
+ protected static final int COLUMN_SIZE = 1;
+ protected static FocusListener repaintListener = new FocusListener() {
+ public void focusGained(final FocusEvent fe) {
+ repaintSelection(fe.getSource());
+ }
+
+ public void focusLost(final FocusEvent fe) {
+ repaintSelection(fe.getSource());
+ }
+
+ protected void repaintSelection(final Object source) {
+ if (source instanceof JList) {
+ repaintListSelection((JList) source);
+ } else if (source instanceof JTable) {
+ repaintTableSelection((JTable) source);
+ }
+ }
+
+ protected void repaintListSelection(final JList> list) {
+ int[] indices = list.getSelectedIndices();
+ for (int i : indices) {
+ Rectangle bounds = list.getCellBounds(i, i);
+ list.repaint(bounds);
+ }
+ }
+
+ protected void repaintTableSelection(final JTable table) {
+ int minRow = table.getSelectionModel().getMinSelectionIndex();
+ int maxRow = table.getSelectionModel().getMaxSelectionIndex();
+ if (minRow == -1 || maxRow == -1) {
+ return;
+ }
+
+ int col0 = table.convertColumnIndexToView(COLUMN_FILENAME);
+
+ Rectangle first = table.getCellRect(minRow, col0, false);
+ Rectangle last = table.getCellRect(maxRow, col0, false);
+ Rectangle dirty = first.union(last);
+ table.repaint(dirty);
+ }
+ };
+ protected Action[] actions;
+ protected int viewType = -1;
+ protected JPanel[] viewPanels = new JPanel[VIEWTYPE_COUNT];
+ protected JPanel currentViewPanel;
+ protected String[] viewTypeActionNames;
+ protected String filesListAccessibleName = null;
+ protected String filesDetailsAccessibleName = null;
+ protected JPopupMenu contextMenu;
+ protected JMenu viewMenu;
+ protected String viewMenuLabelText;
+ protected String refreshActionLabelText;
+ protected String newFolderActionLabelText;
+ protected String kiloByteString;
+ protected String megaByteString;
+ protected String gigaByteString;
+ protected String renameErrorTitleText;
+ protected String renameErrorText;
+ protected String renameErrorFileExistsText;
+ protected boolean smallIconsView = false;
+ protected Border listViewBorder;
+ protected Color listViewBackground;
+ protected boolean listViewWindowsStyle;
+ protected boolean readOnly;
+ protected boolean fullRowSelection = true;
+
+ protected ListSelectionModel listSelectionModel;
+ protected JList> list;
+ protected JTable detailsTable;
+ // Provides a way to recognize a newly created folder, so it can
+ // be selected when it appears in the model.
+ protected File newFolderFile;
+ // Used for accessing methods in the corresponding UI class
+ protected FileChooserUIAccessor fileChooserUIAccessor;
+ protected DetailsTableModel detailsTableModel;
+ protected DetailsTableRowSorter rowSorter;
+ protected final KeyListener detailsKeyListener = new KeyAdapter() {
+ protected final long timeFactor;
+
+ protected final StringBuilder typedString = new StringBuilder();
+
+ protected long lastTime = 1000L;
+
+ {
+ Long l = (Long) UIManager.get("Table.timeFactor");
+ timeFactor = (l != null) ? l : 1000L;
+ }
+
+ /**
+ * Moves the keyboard focus to the first element whose prefix matches
+ * the sequence of alphanumeric keys pressed by the user with delay
+ * less than value of timeFactor
. Subsequent same key
+ * presses move the keyboard focus to the next object that starts with
+ * the same letter until another key is pressed, then it is treated
+ * as the prefix with appropriate number of the same letters followed
+ * by first typed another letter.
+ */
+ public void keyTyped(final KeyEvent e) {
+ BasicDirectoryModel model = getModel();
+ int rowCount = model.getSize();
+
+ if (detailsTable == null || rowCount == 0 ||
+ e.isAltDown() || e.isControlDown() || e.isMetaDown()) {
+ return;
+ }
+
+ InputMap inputMap = detailsTable.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
+ KeyStroke key = KeyStroke.getKeyStrokeForEvent(e);
+
+ if (inputMap != null && inputMap.get(key) != null) {
+ return;
+ }
+
+ int startIndex = detailsTable.getSelectionModel().getLeadSelectionIndex();
+
+ if (startIndex < 0) {
+ startIndex = 0;
+ }
+
+ if (startIndex >= rowCount) {
+ startIndex = rowCount - 1;
+ }
+
+ char c = e.getKeyChar();
+
+ long time = e.getWhen();
+
+ if (time - lastTime < timeFactor) {
+ if (typedString.length() == 1 && typedString.charAt(0) == c) {
+ // Subsequent same key presses move the keyboard focus to the next
+ // object that starts with the same letter.
+ startIndex++;
+ } else {
+ typedString.append(c);
+ }
+ } else {
+ startIndex++;
+
+ typedString.setLength(0);
+ typedString.append(c);
+ }
+
+ lastTime = time;
+
+ if (startIndex >= rowCount) {
+ startIndex = 0;
+ }
+
+ // Find next file
+ int index = getNextMatch(startIndex, rowCount - 1);
+
+ if (index < 0 && startIndex > 0) { // wrap
+ index = getNextMatch(0, startIndex - 1);
+ }
+
+ if (index >= 0) {
+ detailsTable.getSelectionModel().setSelectionInterval(index, index);
+
+ Rectangle cellRect = detailsTable.getCellRect(index, detailsTable.convertColumnIndexToView(COLUMN_FILENAME), false);
+ detailsTable.scrollRectToVisible(cellRect);
+ }
+ }
+
+ protected int getNextMatch(final int startIndex, final int finishIndex) {
+ BasicDirectoryModel model = getModel();
+ JFileChooser fileChooser = getFileChooser();
+ DetailsTableRowSorter rowSorter = getRowSorter();
+
+ String prefix = typedString.toString().toLowerCase();
+
+ // Search element
+ for (int index = startIndex; index <= finishIndex; index++) {
+ File file = (File) model.getElementAt(rowSorter.convertRowIndexToModel(index));
+
+ String fileName = fileChooser.getName(file).toLowerCase();
+
+ if (fileName.startsWith(prefix)) {
+ return index;
+ }
+ }
+
+ return -1;
+ }
+ };
+ protected DetailsTableCellEditor tableCellEditor;
+ protected Action newFolderAction;
+ protected Handler handler;
+ int lastIndex = -1;
+ File editFile = null;
+ JTextField editCell = null;
+ protected FocusListener editorFocusListener = new FocusAdapter() {
+ public void focusLost(final FocusEvent e) {
+ if (!e.isTemporary()) {
+ applyEdit();
+ }
+ }
+ };
+
+ public DarkFilePaneUIBridge(final FileChooserUIAccessor fileChooserUIAccessor) {
+ super(new BorderLayout());
+
+ this.fileChooserUIAccessor = fileChooserUIAccessor;
+
+ installDefaults();
+ createActionMap();
+ }
+
+ protected static void recursivelySetInheritsPopupMenu(final Container container, final boolean b) {
+ if (container instanceof JComponent) {
+ ((JComponent) container).setInheritsPopupMenu(b);
+ }
+ int n = container.getComponentCount();
+ for (int i = 0; i < n; i++) {
+ recursivelySetInheritsPopupMenu((Container) container.getComponent(i), b);
+ }
+ }
+
+ public static void addActionsToMap(final ActionMap map, final Action[] actions) {
+ if (map != null && actions != null) {
+ for (Action a : actions) {
+ String cmd = (String) a.getValue(Action.ACTION_COMMAND_KEY);
+ if (cmd == null) {
+ cmd = (String) a.getValue(Action.NAME);
+ }
+ map.put(cmd, a);
+ }
+ }
+ }
+
+ public static boolean canWrite(final File f, final JFileChooser chooser) {
+ // Return false for non FileSystem files or if file doesn't exist.
+ if (!f.exists()) {
+ return false;
+ }
+
+ try {
+ if (f instanceof ShellFolder) {
+ return f.canWrite();
+ } else {
+ if (usesShellFolder(chooser)) {
+ try {
+ return ShellFolder.getShellFolder(f).canWrite();
+ } catch (FileNotFoundException ex) {
+ // File doesn't exist
+ return false;
+ }
+ } else {
+ // Ordinary file
+ return f.canWrite();
+ }
+ }
+ } catch (SecurityException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Returns true if specified FileChooser should use ShellFolder
+ */
+ public static boolean usesShellFolder(final JFileChooser chooser) {
+ Boolean prop = (Boolean) chooser.getClientProperty("FileChooser.useShellFolder");
+
+ return prop == null ? chooser.getFileSystemView().equals(FileSystemView.getFileSystemView())
+ : prop.booleanValue();
+ }
+
+ public void uninstallUI() {
+ if (getModel() != null) {
+ getModel().removePropertyChangeListener(this);
+ }
+ }
+
+ protected BasicDirectoryModel getModel() {
+ return fileChooserUIAccessor.getModel();
+ }
+
+ public int getViewType() {
+ return viewType;
+ }
+
+ public void setViewType(final int viewType) {
+ if (viewType == this.viewType) {
+ return;
+ }
+
+ int oldValue = this.viewType;
+ this.viewType = viewType;
+
+ JPanel createdViewPanel = null;
+ Component newFocusOwner = null;
+
+ switch (viewType) {
+ case VIEWTYPE_LIST:
+ if (viewPanels[viewType] == null) {
+ createdViewPanel = fileChooserUIAccessor.createList();
+ if (createdViewPanel == null) {
+ createdViewPanel = createList();
+ }
+
+ list = findChildComponent(createdViewPanel, JList.class);
+ if (listSelectionModel == null) {
+ listSelectionModel = list.getSelectionModel();
+ if (detailsTable != null) {
+ detailsTable.setSelectionModel(listSelectionModel);
+ }
+ } else {
+ list.setSelectionModel(listSelectionModel);
+ }
+ }
+ list.setLayoutOrientation(JList.VERTICAL_WRAP);
+ newFocusOwner = list;
+ break;
+
+ case VIEWTYPE_DETAILS:
+ if (viewPanels[viewType] == null) {
+ createdViewPanel = fileChooserUIAccessor.createDetailsView();
+ if (createdViewPanel == null) {
+ createdViewPanel = createDetailsView();
+ }
+
+ detailsTable = findChildComponent(createdViewPanel, JTable.class);
+ if (listSelectionModel != null) {
+ detailsTable.setSelectionModel(listSelectionModel);
+ }
+ }
+ newFocusOwner = detailsTable;
+ break;
+ }
+
+ if (createdViewPanel != null) {
+ viewPanels[viewType] = createdViewPanel;
+ recursivelySetInheritsPopupMenu(createdViewPanel, true);
+ }
+
+ boolean isFocusOwner = false;
+
+ if (currentViewPanel != null) {
+ Component owner = DefaultKeyboardFocusManager.
+ getCurrentKeyboardFocusManager().getPermanentFocusOwner();
+
+ isFocusOwner = owner == detailsTable || owner == list;
+
+ remove(currentViewPanel);
+ }
+
+ currentViewPanel = viewPanels[viewType];
+ add(currentViewPanel, BorderLayout.CENTER);
+
+ if (isFocusOwner && newFocusOwner != null) {
+ newFocusOwner.requestFocusInWindow();
+ }
+
+ revalidate();
+ repaint();
+ updateViewMenu();
+ firePropertyChange("viewType", oldValue, viewType);
+ }
+
+ public Action getViewTypeAction(final int viewType) {
+ return new ViewTypeAction(viewType);
+ }
+
+ protected void installDefaults() {
+ Locale l = getFileChooser().getLocale();
+
+ listViewBorder = UIManager.getBorder("FileChooser.listViewBorder");
+ listViewBackground = UIManager.getColor("FileChooser.listViewBackground");
+ listViewWindowsStyle = UIManager.getBoolean("FileChooser.listViewWindowsStyle");
+ readOnly = UIManager.getBoolean("FileChooser.readOnly");
+
+ // TODO: On windows, get the following localized strings from the OS
+
+ viewMenuLabelText =
+ UIManager.getString("FileChooser.viewMenuLabelText", l);
+ refreshActionLabelText =
+ UIManager.getString("FileChooser.refreshActionLabelText", l);
+ newFolderActionLabelText =
+ UIManager.getString("FileChooser.newFolderActionLabelText", l);
+
+ viewTypeActionNames = new String[VIEWTYPE_COUNT];
+ viewTypeActionNames[VIEWTYPE_LIST] =
+ UIManager.getString("FileChooser.listViewActionLabelText", l);
+ viewTypeActionNames[VIEWTYPE_DETAILS] =
+ UIManager.getString("FileChooser.detailsViewActionLabelText", l);
+
+ kiloByteString = UIManager.getString("FileChooser.fileSizeKiloBytes", l);
+ megaByteString = UIManager.getString("FileChooser.fileSizeMegaBytes", l);
+ gigaByteString = UIManager.getString("FileChooser.fileSizeGigaBytes", l);
+ fullRowSelection = UIManager.getBoolean("FileView.fullRowSelection");
+
+ filesListAccessibleName = UIManager.getString("FileChooser.filesListAccessibleName", l);
+ filesDetailsAccessibleName = UIManager.getString("FileChooser.filesDetailsAccessibleName", l);
+
+ renameErrorTitleText = UIManager.getString("FileChooser.renameErrorTitleText", l);
+ renameErrorText = UIManager.getString("FileChooser.renameErrorText", l);
+ renameErrorFileExistsText = UIManager.getString("FileChooser.renameErrorFileExistsText", l);
+ }
+
+ /**
+ * Fetches the command list for the FilePane. These commands
+ * are useful for binding to events, such as in a keymap.
+ *
+ * @return the command list
+ */
+ public Action[] getActions() {
+ if (actions == null) {
+ @SuppressWarnings("serial")
+ // JDK-implementation class
+ class FilePaneAction extends AbstractAction {
+ FilePaneAction(final String name) {
+ this(name, name);
+ }
+
+ FilePaneAction(final String name, final String cmd) {
+ super(name);
+ putValue(Action.ACTION_COMMAND_KEY, cmd);
+ }
+
+ public void actionPerformed(final ActionEvent e) {
+ String cmd = (String) getValue(Action.ACTION_COMMAND_KEY);
+
+ if (Objects.equals(cmd, ACTION_CANCEL)) {
+ if (editFile != null) {
+ cancelEdit();
+ } else {
+ getFileChooser().cancelSelection();
+ }
+ } else if (Objects.equals(cmd, ACTION_EDIT_FILE_NAME)) {
+ JFileChooser fc = getFileChooser();
+ int index = listSelectionModel.getMinSelectionIndex();
+ if (index >= 0 && editFile == null &&
+ (!fc.isMultiSelectionEnabled() ||
+ fc.getSelectedFiles().length <= 1)) {
+
+ editFileName(index);
+ }
+ } else if (Objects.equals(cmd, ACTION_REFRESH)) {
+ getFileChooser().rescanCurrentDirectory();
+ }
+ }
+
+ public boolean isEnabled() {
+ String cmd = (String) getValue(Action.ACTION_COMMAND_KEY);
+ if (Objects.equals(cmd, ACTION_CANCEL)) {
+ return getFileChooser().isEnabled();
+ } else if (Objects.equals(cmd, ACTION_EDIT_FILE_NAME)) {
+ return !readOnly && getFileChooser().isEnabled();
+ } else {
+ return true;
+ }
+ }
+ }
+
+ ArrayList actionList = new ArrayList<>(8);
+ Action action;
+
+ actionList.add(new FilePaneAction(ACTION_CANCEL));
+ actionList.add(new FilePaneAction(ACTION_EDIT_FILE_NAME));
+ actionList.add(new FilePaneAction(refreshActionLabelText, ACTION_REFRESH));
+
+ action = fileChooserUIAccessor.getApproveSelectionAction();
+ if (action != null) {
+ actionList.add(action);
+ }
+ action = fileChooserUIAccessor.getChangeToParentDirectoryAction();
+ if (action != null) {
+ actionList.add(action);
+ }
+ action = getNewFolderAction();
+ if (action != null) {
+ actionList.add(action);
+ }
+ action = getViewTypeAction(VIEWTYPE_LIST);
+ if (action != null) {
+ actionList.add(action);
+ }
+ action = getViewTypeAction(VIEWTYPE_DETAILS);
+ if (action != null) {
+ actionList.add(action);
+ }
+ actions = actionList.toArray(new Action[0]);
+ }
+
+ return Arrays.copyOf(actions, actions.length);
+ }
+
+ protected void createActionMap() {
+ addActionsToMap(super.getActionMap(), getActions());
+ }
+
+ protected void updateListRowCount(final JList> list) {
+ if (smallIconsView) {
+ list.setVisibleRowCount(getModel().getSize() / 3);
+ } else {
+ list.setVisibleRowCount(-1);
+ }
+ }
+
+ public JPanel createList() {
+ JPanel p = new JPanel(new BorderLayout());
+ final JFileChooser fileChooser = getFileChooser();
+
+ @SuppressWarnings("serial") // anonymous class
+ final JList list = new JList<>() {
+ public int getNextMatch(final String prefix, final int startIndex, final Position.Bias bias) {
+ ListModel> model = getModel();
+ int max = model.getSize();
+ if (prefix == null || startIndex < 0 || startIndex >= max) {
+ throw new IllegalArgumentException();
+ }
+ // start search from the next element before/after the selected element
+ boolean backwards = (bias == Position.Bias.Backward);
+ for (int i = startIndex; backwards ? i >= 0 : i < max; i += (backwards ? -1 : 1)) {
+ String filename = fileChooser.getName((File) model.getElementAt(i));
+ if (filename.regionMatches(true, 0, prefix, 0, prefix.length())) {
+ return i;
+ }
+ }
+ return -1;
+ }
+ };
+ list.setCellRenderer(new FileRenderer());
+ list.setLayoutOrientation(JList.VERTICAL_WRAP);
+
+ // 4835633 : tell BasicListUI that this is a file list
+ list.putClientProperty("List.isFileList", Boolean.TRUE);
+
+ if (listViewWindowsStyle) {
+ list.addFocusListener(repaintListener);
+ }
+
+ updateListRowCount(list);
+
+ getModel().addListDataListener(new ListDataListener() {
+ public void intervalAdded(final ListDataEvent e) {
+ updateListRowCount(list);
+ }
+
+ public void intervalRemoved(final ListDataEvent e) {
+ updateListRowCount(list);
+ }
+
+ public void contentsChanged(final ListDataEvent e) {
+ if (isShowing()) {
+ clearSelection();
+ }
+ updateListRowCount(list);
+ }
+ });
+
+ getModel().addPropertyChangeListener(this);
+
+ if (fileChooser.isMultiSelectionEnabled()) {
+ list.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
+ } else {
+ list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+ }
+ list.setModel(new SortableListModel());
+
+ list.addListSelectionListener(createListSelectionListener());
+ list.addMouseListener(getMouseHandler());
+
+ JScrollPane scrollPane = new JScrollPane(list);
+ if (listViewBackground != null) {
+ list.setBackground(listViewBackground);
+ }
+ if (listViewBorder != null) {
+ scrollPane.setBorder(listViewBorder);
+ }
+
+ list.putClientProperty(AccessibleContext.ACCESSIBLE_NAME_PROPERTY, filesListAccessibleName);
+
+ p.add(scrollPane, BorderLayout.CENTER);
+ return p;
+ }
+
+ protected DetailsTableModel getDetailsTableModel() {
+ if (detailsTableModel == null) {
+ detailsTableModel = new DetailsTableModel(getFileChooser());
+ }
+ return detailsTableModel;
+ }
+
+ protected void updateDetailsColumnModel(final JTable table) {
+ if (table != null) {
+ ShellFolderColumnInfo[] columns = detailsTableModel.getColumns();
+
+ TableColumnModel columnModel = new DefaultTableColumnModel();
+ for (int i = 0; i < columns.length; i++) {
+ ShellFolderColumnInfo dataItem = columns[i];
+ TableColumn column = new TableColumn(i);
+
+ String title = dataItem.getTitle();
+ if (title != null && title.startsWith("FileChooser.") && title.endsWith("HeaderText")) {
+ // the column must have a string resource that we try to get
+ String uiTitle = UIManager.getString(title, table.getLocale());
+ if (uiTitle != null) {
+ title = uiTitle;
+ }
+ }
+ column.setHeaderValue(title);
+
+ Integer width = dataItem.getWidth();
+ if (width != null) {
+ column.setPreferredWidth(width);
+ // otherwise we let JTable to decide the actual width
+ }
+
+ columnModel.addColumn(column);
+ }
+
+ // Install cell editor for editing file name
+ if (!readOnly && columnModel.getColumnCount() > COLUMN_FILENAME) {
+ columnModel.getColumn(COLUMN_FILENAME).
+ setCellEditor(getDetailsTableCellEditor());
+ }
+
+ table.setColumnModel(columnModel);
+ table.getColumnModel().setColumnMargin(0);
+ }
+ }
+
+ protected DetailsTableCellEditor getDetailsTableCellEditor() {
+ if (tableCellEditor == null) {
+ tableCellEditor = new DetailsTableCellEditor(new JTextField() {
+ {
+ super.setBorder(new TextFieldTableCellEditorBorder());
+ }
+
+ @Override
+ public void setBorder(final Border border) {
+ }
+ });
+ }
+ return tableCellEditor;
+ }
+
+ public JPanel createDetailsView() {
+ final JFileChooser chooser = getFileChooser();
+
+ JPanel p = new JPanel(new BorderLayout());
+
+ @SuppressWarnings("serial") // anonymous class
+ final JTable detailsTable = new JTable(getDetailsTableModel()) {
+ public void tableChanged(final TableModelEvent e) {
+ super.tableChanged(e);
+
+ if (e.getFirstRow() == TableModelEvent.HEADER_ROW) {
+ // update header with possibly changed column set
+ updateDetailsColumnModel(this);
+ }
+ }
+
+ // Handle Escape key events here
+ protected boolean processKeyBinding(final KeyStroke ks, final KeyEvent e, final int condition, final boolean pressed) {
+ if (e.getKeyCode() == KeyEvent.VK_ESCAPE && getCellEditor() == null) {
+ // We are not editing, forward to filechooser.
+ chooser.dispatchEvent(e);
+ return true;
+ }
+ return super.processKeyBinding(ks, e, condition, pressed);
+ }
+ };
+
+ detailsTable.setRowSorter(getRowSorter());
+ detailsTable.setAutoCreateColumnsFromModel(false);
+ detailsTable.setComponentOrientation(chooser.getComponentOrientation());
+ detailsTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
+ detailsTable.setShowGrid(false);
+ detailsTable.putClientProperty("JTable.autoStartsEdit", Boolean.FALSE);
+ detailsTable.addKeyListener(detailsKeyListener);
+
+ Font font = list.getFont();
+ detailsTable.setFont(font);
+
+ TableCellRenderer headerRenderer =
+ new AlignableTableHeaderRenderer(detailsTable.getTableHeader().getDefaultRenderer());
+ detailsTable.getTableHeader().setDefaultRenderer(headerRenderer);
+ TableCellRenderer cellRenderer = new DetailsTableCellRenderer(chooser);
+ detailsTable.setDefaultRenderer(Object.class, cellRenderer);
+
+ // So that drag can be started on a mouse press
+ detailsTable.getColumnModel().getSelectionModel().
+ setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+
+ detailsTable.addMouseListener(getMouseHandler());
+ // No need to addListSelectionListener because selections are forwarded
+ // to our JList.
+
+ // 4835633 : tell BasicTableUI that this is a file list
+ detailsTable.putClientProperty("Table.isFileList", Boolean.TRUE);
+
+ if (listViewWindowsStyle) {
+ detailsTable.addFocusListener(repaintListener);
+ }
+
+ // TAB/SHIFT-TAB should transfer focus and ENTER should select an item.
+ // We don't want them to navigate within the table
+ ActionMap am = SwingUtilities.getUIActionMap(detailsTable);
+ am.remove("selectNextRowCell");
+ am.remove("selectPreviousRowCell");
+ am.remove("selectNextColumnCell");
+ am.remove("selectPreviousColumnCell");
+ detailsTable.setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS,
+ null);
+ detailsTable.setFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS,
+ null);
+
+ JScrollPane scrollpane = new JScrollPane(detailsTable);
+ scrollpane.setComponentOrientation(chooser.getComponentOrientation());
+ LookAndFeel.installColors(scrollpane.getViewport(), "Table.background", "Table.foreground");
+
+ // Adjust width of first column so the table fills the viewport when
+ // first displayed (temporary listener).
+ scrollpane.addComponentListener(new ComponentAdapter() {
+ public void componentResized(final ComponentEvent e) {
+ JScrollPane sp = (JScrollPane) e.getComponent();
+ fixNameColumnWidth(sp.getViewport().getSize().width);
+ sp.removeComponentListener(this);
+ }
+ });
+
+ // 4835633.
+ // If the mouse is pressed in the area below the Details view table, the
+ // event is not dispatched to the Table MouseListener but to the
+ // scrollpane. Listen for that here so we can clear the selection.
+ scrollpane.addMouseListener(new MouseAdapter() {
+ public void mousePressed(final MouseEvent e) {
+ JScrollPane jsp = ((JScrollPane) e.getComponent());
+ JTable table = (JTable) jsp.getViewport().getView();
+
+ if (!e.isShiftDown() || table.getSelectionModel().getSelectionMode() == ListSelectionModel.SINGLE_SELECTION) {
+ clearSelection();
+ TableCellEditor tce = table.getCellEditor();
+ if (tce != null) {
+ tce.stopCellEditing();
+ }
+ }
+ }
+ });
+
+ detailsTable.setForeground(list.getForeground());
+ detailsTable.setBackground(list.getBackground());
+
+ if (listViewBorder != null) {
+ scrollpane.setBorder(listViewBorder);
+ }
+ p.add(scrollpane, BorderLayout.CENTER);
+
+ detailsTableModel.fireTableStructureChanged();
+
+ detailsTable.putClientProperty(AccessibleContext.ACCESSIBLE_NAME_PROPERTY, filesDetailsAccessibleName);
+
+ return p;
+ } // createDetailsView
+
+ protected void fixNameColumnWidth(final int viewWidth) {
+ TableColumn nameCol = detailsTable.getColumnModel().getColumn(COLUMN_FILENAME);
+ int tableWidth = detailsTable.getPreferredSize().width;
+
+ if (tableWidth < viewWidth) {
+ nameCol.setPreferredWidth(nameCol.getPreferredWidth() + viewWidth - tableWidth);
+ }
+ }
+
+ /**
+ * Creates a selection listener for the list of files and directories.
+ *
+ * @return a ListSelectionListener
+ */
+ public ListSelectionListener createListSelectionListener() {
+ return fileChooserUIAccessor.createListSelectionListener();
+ }
+
+ protected int getEditIndex() {
+ return lastIndex;
+ }
+
+ protected void setEditIndex(final int i) {
+ lastIndex = i;
+ }
+
+ protected void resetEditIndex() {
+ lastIndex = -1;
+ }
+
+ protected void cancelEdit() {
+ if (editFile != null) {
+ editFile = null;
+ list.remove(editCell);
+ repaint();
+ } else if (detailsTable != null && detailsTable.isEditing()) {
+ detailsTable.getCellEditor().cancelCellEditing();
+ }
+ }
+
+ /**
+ * @param index visual index of the file to be edited
+ */
+ @SuppressWarnings("deprecation")
+ protected void editFileName(final int index) {
+ JFileChooser chooser = getFileChooser();
+ File currentDirectory = chooser.getCurrentDirectory();
+
+ if (readOnly || !canWrite(currentDirectory, chooser)) {
+ return;
+ }
+
+ ensureIndexIsVisible(index);
+ switch (viewType) {
+ case VIEWTYPE_LIST:
+ editFile = (File) getModel().getElementAt(getRowSorter().convertRowIndexToModel(index));
+ Rectangle r = list.getCellBounds(index, index);
+ if (editCell == null) {
+ editCell = new JTextField();
+ editCell.setName("Tree.cellEditor");
+ editCell.addActionListener(new EditActionListener());
+ editCell.addFocusListener(editorFocusListener);
+ editCell.setNextFocusableComponent(list);
+ }
+ list.add(editCell);
+ editCell.setText(chooser.getName(editFile));
+ ComponentOrientation orientation = list.getComponentOrientation();
+ editCell.setComponentOrientation(orientation);
+
+ Icon icon = chooser.getIcon(editFile);
+
+ // PENDING - grab padding (4) below from defaults table.
+ int editX = icon == null ? 20 : icon.getIconWidth() + 4;
+
+ if (orientation.isLeftToRight()) {
+ editCell.setBounds(editX + r.x, r.y, r.width - editX, r.height);
+ } else {
+ editCell.setBounds(r.x, r.y, r.width - editX, r.height);
+ }
+ editCell.requestFocus();
+ editCell.selectAll();
+ break;
+
+ case VIEWTYPE_DETAILS:
+ detailsTable.editCellAt(index, COLUMN_FILENAME);
+ break;
+ }
+ }
+
+ protected void applyEdit() {
+ if (editFile != null && editFile.exists()) {
+ JFileChooser chooser = getFileChooser();
+ String oldDisplayName = chooser.getName(editFile);
+ String oldFileName = editFile.getName();
+ String newDisplayName = editCell.getText().trim();
+ String newFileName;
+
+ if (!newDisplayName.equals(oldDisplayName)) {
+ newFileName = newDisplayName;
+ //Check if extension is hidden from user
+ int i1 = oldFileName.length();
+ int i2 = oldDisplayName.length();
+ if (i1 > i2 && oldFileName.charAt(i2) == '.') {
+ newFileName = newDisplayName + oldFileName.substring(i2);
+ }
+
+ // rename
+ FileSystemView fsv = chooser.getFileSystemView();
+ File f2 = fsv.createFileObject(editFile.getParentFile(), newFileName);
+ if (f2.exists()) {
+ JOptionPane.showMessageDialog(chooser, MessageFormat.format(renameErrorFileExistsText, oldFileName),
+ renameErrorTitleText, JOptionPane.ERROR_MESSAGE);
+ } else {
+ if (getModel().renameFile(editFile, f2)) {
+ if (fsv.isParent(chooser.getCurrentDirectory(), f2)) {
+ if (chooser.isMultiSelectionEnabled()) {
+ chooser.setSelectedFiles(new File[]{f2});
+ } else {
+ chooser.setSelectedFile(f2);
+ }
+ } else {
+ //Could be because of delay in updating Desktop folder
+ //chooser.setSelectedFile(null);
+ }
+ } else {
+ JOptionPane.showMessageDialog(chooser, MessageFormat.format(renameErrorText, oldFileName),
+ renameErrorTitleText, JOptionPane.ERROR_MESSAGE);
+ }
+ }
+ }
+ }
+ if (detailsTable != null && detailsTable.isEditing()) {
+ detailsTable.getCellEditor().stopCellEditing();
+ }
+ cancelEdit();
+ }
+
+ @SuppressWarnings("serial") // anonymous class inside
+ public Action getNewFolderAction() {
+ if (!readOnly && newFolderAction == null) {
+ newFolderAction = new AbstractAction(newFolderActionLabelText) {
+ protected Action basicNewFolderAction;
+
+ // Initializer
+ {
+ putValue(Action.ACTION_COMMAND_KEY, FilePane.ACTION_NEW_FOLDER);
+
+ File currentDirectory = getFileChooser().getCurrentDirectory();
+ if (currentDirectory != null) {
+ setEnabled(canWrite(currentDirectory, getFileChooser()));
+ }
+ }
+
+ public void actionPerformed(final ActionEvent ev) {
+ if (basicNewFolderAction == null) {
+ basicNewFolderAction = fileChooserUIAccessor.getNewFolderAction();
+ }
+ JFileChooser fc = getFileChooser();
+ File oldFile = fc.getSelectedFile();
+ basicNewFolderAction.actionPerformed(ev);
+ File newFile = fc.getSelectedFile();
+ if (newFile != null && !newFile.equals(oldFile) && newFile.isDirectory()) {
+ newFolderFile = newFile;
+ }
+ }
+ };
+ }
+ return newFolderAction;
+ }
+
+ @SuppressWarnings("deprecation")
+ void setFileSelected() {
+ if (getFileChooser().isMultiSelectionEnabled() && !isDirectorySelected()) {
+ File[] files = getFileChooser().getSelectedFiles(); // Should be selected
+ Object[] selectedObjects = list.getSelectedValues(); // Are actually selected
+
+ listSelectionModel.setValueIsAdjusting(true);
+ try {
+ int lead = listSelectionModel.getLeadSelectionIndex();
+ int anchor = listSelectionModel.getAnchorSelectionIndex();
+
+ Arrays.sort(files);
+ Arrays.sort(selectedObjects);
+
+ int shouldIndex = 0;
+ int actuallyIndex = 0;
+
+ // Remove files that shouldn't be selected and add files which should be selected
+ // Note: Assume files are already sorted in compareTo order.
+ while (shouldIndex < files.length &&
+ actuallyIndex < selectedObjects.length) {
+ int comparison = files[shouldIndex].compareTo((File) selectedObjects[actuallyIndex]);
+ if (comparison < 0) {
+ doSelectFile(files[shouldIndex++]);
+ } else if (comparison > 0) {
+ doDeselectFile(selectedObjects[actuallyIndex++]);
+ } else {
+ // Do nothing
+ shouldIndex++;
+ actuallyIndex++;
+ }
+
+ }
+
+ while (shouldIndex < files.length) {
+ doSelectFile(files[shouldIndex++]);
+ }
+
+ while (actuallyIndex < selectedObjects.length) {
+ doDeselectFile(selectedObjects[actuallyIndex++]);
+ }
+
+ // restore the anchor and lead
+ if (listSelectionModel instanceof DefaultListSelectionModel) {
+ ((DefaultListSelectionModel) listSelectionModel).
+ moveLeadSelectionIndex(lead);
+ listSelectionModel.setAnchorSelectionIndex(anchor);
+ }
+ } finally {
+ listSelectionModel.setValueIsAdjusting(false);
+ }
+ } else {
+ JFileChooser chooser = getFileChooser();
+ File f;
+ if (isDirectorySelected()) {
+ f = getDirectory();
+ } else {
+ f = chooser.getSelectedFile();
+ }
+ int i;
+ if (f != null && (i = getModel().indexOf(f)) >= 0) {
+ int viewIndex = getRowSorter().convertRowIndexToView(i);
+ listSelectionModel.setSelectionInterval(viewIndex, viewIndex);
+ ensureIndexIsVisible(viewIndex);
+ } else {
+ clearSelection();
+ }
+ }
+ }
+
+ protected void doSelectFile(final File fileToSelect) {
+ int index = getModel().indexOf(fileToSelect);
+ // could be missed in the current directory if it changed
+ if (index >= 0) {
+ index = getRowSorter().convertRowIndexToView(index);
+ listSelectionModel.addSelectionInterval(index, index);
+ }
+ }
+
+ protected void doDeselectFile(final Object fileToDeselect) {
+ int index = getRowSorter().convertRowIndexToView(
+ getModel().indexOf(fileToDeselect));
+ listSelectionModel.removeSelectionInterval(index, index);
+ }
+
+ protected void doSelectedFileChanged(final PropertyChangeEvent e) {
+ applyEdit();
+ File f = (File) e.getNewValue();
+ JFileChooser fc = getFileChooser();
+ if (f != null
+ && ((fc.isFileSelectionEnabled() && !f.isDirectory())
+ || (f.isDirectory() && fc.isDirectorySelectionEnabled()))) {
+
+ setFileSelected();
+ }
+ }
+
+ protected void doSelectedFilesChanged(final PropertyChangeEvent e) {
+ applyEdit();
+ File[] files = (File[]) e.getNewValue();
+ JFileChooser fc = getFileChooser();
+ if (files != null
+ && files.length > 0
+ && (files.length > 1 || fc.isDirectorySelectionEnabled() || !files[0].isDirectory())) {
+ setFileSelected();
+ }
+ }
+
+ protected void doDirectoryChanged(final PropertyChangeEvent e) {
+ getDetailsTableModel().updateColumnInfo();
+
+ JFileChooser fc = getFileChooser();
+ FileSystemView fsv = fc.getFileSystemView();
+
+ applyEdit();
+ resetEditIndex();
+ ensureIndexIsVisible(0);
+ File currentDirectory = fc.getCurrentDirectory();
+ if (currentDirectory != null) {
+ if (!readOnly) {
+ getNewFolderAction().setEnabled(canWrite(currentDirectory, getFileChooser()));
+ }
+ fileChooserUIAccessor.getChangeToParentDirectoryAction().setEnabled(!fsv.isRoot(currentDirectory));
+ }
+ if (list != null) {
+ list.clearSelection();
+ }
+ }
+
+ protected void doFilterChanged(final PropertyChangeEvent e) {
+ applyEdit();
+ resetEditIndex();
+ clearSelection();
+ }
+
+ protected void doFileSelectionModeChanged(final PropertyChangeEvent e) {
+ applyEdit();
+ resetEditIndex();
+ clearSelection();
+ }
+
+ protected void doMultiSelectionChanged(final PropertyChangeEvent e) {
+ if (getFileChooser().isMultiSelectionEnabled()) {
+ listSelectionModel.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
+ } else {
+ listSelectionModel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+ clearSelection();
+ getFileChooser().setSelectedFiles(null);
+ }
+ }
+
+ /*
+ * Listen for filechooser property changes, such as
+ * the selected file changing, or the type of the dialog changing.
+ */
+ public void propertyChange(final PropertyChangeEvent e) {
+ if (viewType == -1) {
+ setViewType(VIEWTYPE_LIST);
+ }
+
+ String s = e.getPropertyName();
+ if (s.equals(JFileChooser.SELECTED_FILE_CHANGED_PROPERTY)) {
+ doSelectedFileChanged(e);
+ } else if (s.equals(JFileChooser.SELECTED_FILES_CHANGED_PROPERTY)) {
+ doSelectedFilesChanged(e);
+ } else if (s.equals(JFileChooser.DIRECTORY_CHANGED_PROPERTY)) {
+ doDirectoryChanged(e);
+ } else if (s.equals(JFileChooser.FILE_FILTER_CHANGED_PROPERTY)) {
+ doFilterChanged(e);
+ } else if (s.equals(JFileChooser.FILE_SELECTION_MODE_CHANGED_PROPERTY)) {
+ doFileSelectionModeChanged(e);
+ } else if (s.equals(JFileChooser.MULTI_SELECTION_ENABLED_CHANGED_PROPERTY)) {
+ doMultiSelectionChanged(e);
+ } else if (s.equals(JFileChooser.CANCEL_SELECTION)) {
+ applyEdit();
+ } else if (s.equals("busy")) {
+ setCursor((Boolean) e.getNewValue() ? waitCursor : null);
+ } else if (s.equals("componentOrientation")) {
+ ComponentOrientation o = (ComponentOrientation) e.getNewValue();
+ JFileChooser cc = (JFileChooser) e.getSource();
+ if (o != e.getOldValue()) {
+ cc.applyComponentOrientation(o);
+ }
+ if (detailsTable != null) {
+ detailsTable.setComponentOrientation(o);
+ detailsTable.getParent().getParent().setComponentOrientation(o);
+ }
+ }
+ }
+
+ public void ensureFileIsVisible(final JFileChooser fc, final File f) {
+ int modelIndex = getModel().indexOf(f);
+ if (modelIndex >= 0) {
+ ensureIndexIsVisible(getRowSorter().convertRowIndexToView(modelIndex));
+ }
+ }
+
+ protected void ensureIndexIsVisible(final int i) {
+ if (i >= 0) {
+ if (list != null) {
+ list.ensureIndexIsVisible(i);
+ }
+ if (detailsTable != null) {
+ detailsTable.scrollRectToVisible(detailsTable.getCellRect(i, COLUMN_FILENAME, true));
+ }
+ }
+ }
+
+ protected DetailsTableRowSorter getRowSorter() {
+ if (rowSorter == null) {
+ rowSorter = new DetailsTableRowSorter();
+ }
+ return rowSorter;
+ }
+
+ /* The following methods are used by the PropertyChange Listener */
+
+ public void rescanCurrentDirectory() {
+ getModel().validateFileCache();
+ }
+
+ public void clearSelection() {
+ if (listSelectionModel != null) {
+ listSelectionModel.clearSelection();
+ if (listSelectionModel instanceof DefaultListSelectionModel) {
+ ((DefaultListSelectionModel) listSelectionModel).moveLeadSelectionIndex(-1);
+ listSelectionModel.setAnchorSelectionIndex(-1);
+ }
+ }
+ }
+
+ public JPopupMenu getComponentPopupMenu() {
+ JPopupMenu popupMenu = getFileChooser().getComponentPopupMenu();
+ if (popupMenu != null) {
+ return popupMenu;
+ }
+
+ JMenu viewMenu = getViewMenu();
+ if (contextMenu == null) {
+ contextMenu = new JPopupMenu();
+ if (viewMenu != null) {
+ contextMenu.add(viewMenu);
+ if (listViewWindowsStyle) {
+ contextMenu.addSeparator();
+ }
+ }
+ ActionMap actionMap = getActionMap();
+ Action refreshAction = actionMap.get(ACTION_REFRESH);
+ Action newFolderAction = actionMap.get(ACTION_NEW_FOLDER);
+ if (refreshAction != null) {
+ contextMenu.add(refreshAction);
+ if (listViewWindowsStyle && newFolderAction != null) {
+ contextMenu.addSeparator();
+ }
+ }
+ if (newFolderAction != null) {
+ contextMenu.add(newFolderAction);
+ }
+ }
+ if (viewMenu != null) {
+ viewMenu.getPopupMenu().setInvoker(viewMenu);
+ }
+ return contextMenu;
+ }
+
+ protected JFileChooser getFileChooser() {
+ return fileChooserUIAccessor.getFileChooser();
+ }
+
+ public JMenu getViewMenu() {
+ if (viewMenu == null) {
+ viewMenu = new JMenu(viewMenuLabelText);
+ ButtonGroup viewButtonGroup = new ButtonGroup();
+
+ for (int i = 0; i < VIEWTYPE_COUNT; i++) {
+ JRadioButtonMenuItem mi =
+ new JRadioButtonMenuItem(new ViewTypeAction(i));
+ viewButtonGroup.add(mi);
+ viewMenu.add(mi);
+ }
+ updateViewMenu();
+ }
+ return viewMenu;
+ }
+
+ protected void updateViewMenu() {
+ if (viewMenu != null) {
+ Component[] comps = viewMenu.getMenuComponents();
+ for (Component comp : comps) {
+ if (comp instanceof JRadioButtonMenuItem) {
+ JRadioButtonMenuItem mi = (JRadioButtonMenuItem) comp;
+ if (((ViewTypeAction) mi.getAction()).viewType == viewType) {
+ mi.setSelected(true);
+ }
+ }
+ }
+ }
+ }
+
+ protected Handler getMouseHandler() {
+ if (handler == null) {
+ handler = new Handler();
+ }
+ return handler;
+ }
+
+ /**
+ * Property to remember whether a directory is currently selected in the UI.
+ *
+ * @return true
iff a directory is currently selected.
+ */
+ protected boolean isDirectorySelected() {
+ return fileChooserUIAccessor.isDirectorySelected();
+ }
+
+ /**
+ * Property to remember the directory that is currently selected in the UI.
+ *
+ * @return the value of the directory
property
+ */
+ protected File getDirectory() {
+ return fileChooserUIAccessor.getDirectory();
+ }
+
+ protected T findChildComponent(final Container container, final Class cls) {
+ int n = container.getComponentCount();
+ for (int i = 0; i < n; i++) {
+ Component comp = container.getComponent(i);
+ if (cls.isInstance(comp)) {
+ return cls.cast(comp);
+ } else if (comp instanceof Container) {
+ T c = findChildComponent((Container) comp, cls);
+ if (c != null) {
+ return c;
+ }
+ }
+ }
+ return null;
+ }
+
+ // This interface is used to access methods in the FileChooserUI
+ // that are not public.
+ public interface FileChooserUIAccessor {
+ JFileChooser getFileChooser();
+
+ BasicDirectoryModel getModel();
+
+ JPanel createList();
+
+ JPanel createDetailsView();
+
+ boolean isDirectorySelected();
+
+ File getDirectory();
+
+ Action getApproveSelectionAction();
+
+ Action getChangeToParentDirectoryAction();
+
+ Action getNewFolderAction();
+
+ MouseListener createDoubleClickListener(JList> list);
+
+ ListSelectionListener createListSelectionListener();
+ }
+
+ @SuppressWarnings("serial")
+ // JDK-implementation class
+ class ViewTypeAction extends AbstractAction {
+ protected int viewType;
+
+ ViewTypeAction(final int viewType) {
+ super(viewTypeActionNames[viewType]);
+ this.viewType = viewType;
+
+ String cmd;
+ switch (viewType) {
+ case VIEWTYPE_LIST:
+ cmd = ACTION_VIEW_LIST;
+ break;
+ case VIEWTYPE_DETAILS:
+ cmd = ACTION_VIEW_DETAILS;
+ break;
+ default:
+ cmd = (String) getValue(Action.NAME);
+ }
+ putValue(Action.ACTION_COMMAND_KEY, cmd);
+ }
+
+ public void actionPerformed(final ActionEvent e) {
+ setViewType(viewType);
+ }
+ }
+
+ /**
+ * This model allows for sorting JList
+ */
+ @SuppressWarnings("serial") // JDK-implementation class
+ protected class SortableListModel extends AbstractListModel
+ implements TableModelListener, RowSorterListener {
+
+ public SortableListModel() {
+ getDetailsTableModel().addTableModelListener(this);
+ getRowSorter().addRowSorterListener(this);
+ }
+
+ public void tableChanged(final TableModelEvent e) {
+ fireContentsChanged(this, 0, getSize());
+ }
+
+ public int getSize() {
+ return getModel().getSize();
+ }
+
+ public Object getElementAt(final int index) {
+ // JList doesn't support RowSorter so far, so we put it into the list model
+ return getModel().getElementAt(getRowSorter().convertRowIndexToModel(index));
+ }
+
+ public void sorterChanged(final RowSorterEvent e) {
+ fireContentsChanged(this, 0, getSize());
+ }
+ }
+
+ @SuppressWarnings("serial")
+ // JDK-implementation class
+ class DetailsTableModel extends AbstractTableModel implements ListDataListener {
+ JFileChooser chooser;
+ BasicDirectoryModel directoryModel;
+
+ ShellFolderColumnInfo[] columns;
+ int[] columnMap;
+
+ DetailsTableModel(final JFileChooser fc) {
+ this.chooser = fc;
+ directoryModel = getModel();
+ directoryModel.addListDataListener(this);
+
+ updateColumnInfo();
+ }
+
+ void updateColumnInfo() {
+ File dir = chooser.getCurrentDirectory();
+ if (dir != null && usesShellFolder(chooser)) {
+ try {
+ dir = ShellFolder.getShellFolder(dir);
+ } catch (FileNotFoundException e) {
+ // Leave dir without changing
+ }
+ }
+
+ ShellFolderColumnInfo[] allColumns = ShellFolder.getFolderColumns(dir);
+
+ ArrayList visibleColumns = new ArrayList<>();
+ columnMap = new int[allColumns.length];
+ for (int i = 0; i < allColumns.length; i++) {
+ ShellFolderColumnInfo column = allColumns[i];
+ if (column.isVisible()) {
+ columnMap[visibleColumns.size()] = i;
+ visibleColumns.add(column);
+ }
+ }
+
+ columns = new ShellFolderColumnInfo[visibleColumns.size()];
+ visibleColumns.toArray(columns);
+ columnMap = Arrays.copyOf(columnMap, columns.length);
+
+ List extends RowSorter.SortKey> sortKeys = (rowSorter == null) ? null : rowSorter.getSortKeys();
+ fireTableStructureChanged();
+ restoreSortKeys(sortKeys);
+ }
+
+ protected void restoreSortKeys(List extends RowSorter.SortKey> sortKeys) {
+ if (sortKeys != null) {
+ // check if preserved sortKeys are valid for this folder
+ for (int i = 0; i < sortKeys.size(); i++) {
+ RowSorter.SortKey sortKey = sortKeys.get(i);
+ if (sortKey.getColumn() >= columns.length) {
+ sortKeys = null;
+ break;
+ }
+ }
+ if (sortKeys != null) {
+ rowSorter.setSortKeys(sortKeys);
+ }
+ }
+ }
+
+ public int getRowCount() {
+ return directoryModel.getSize();
+ }
+
+ public int getColumnCount() {
+ return columns.length;
+ }
+
+ public Object getValueAt(final int row, final int col) {
+ // Note: It is very important to avoid getting info on drives, as
+ // this will trigger "No disk in A:" and similar dialogs.
+ //
+ // Use (f.exists() && !chooser.getFileSystemView().isFileSystemRoot(f)) to
+ // determine if it is safe to call methods directly on f.
+ return getFileColumnValue((File) directoryModel.getElementAt(row), col);
+ }
+
+ public boolean isCellEditable(final int row, final int column) {
+ File currentDirectory = getFileChooser().getCurrentDirectory();
+ return (!readOnly && column == COLUMN_FILENAME
+ && canWrite(currentDirectory, getFileChooser()));
+ }
+
+ public void setValueAt(final Object value, final int row, final int col) {
+ if (col == COLUMN_FILENAME) {
+ final JFileChooser chooser = getFileChooser();
+ File f = (File) getValueAt(row, col);
+ if (f != null) {
+ String oldDisplayName = chooser.getName(f);
+ String oldFileName = f.getName();
+ String newDisplayName = ((String) value).trim();
+ String newFileName;
+
+ if (!newDisplayName.equals(oldDisplayName)) {
+ newFileName = newDisplayName;
+ //Check if extension is hidden from user
+ int i1 = oldFileName.length();
+ int i2 = oldDisplayName.length();
+ if (i1 > i2 && oldFileName.charAt(i2) == '.') {
+ newFileName = newDisplayName + oldFileName.substring(i2);
+ }
+
+ // rename
+ FileSystemView fsv = chooser.getFileSystemView();
+ final File f2 = fsv.createFileObject(f.getParentFile(), newFileName);
+ if (f2.exists()) {
+ JOptionPane.showMessageDialog(chooser, MessageFormat.format(renameErrorFileExistsText,
+ oldFileName), renameErrorTitleText,
+ JOptionPane.ERROR_MESSAGE);
+ } else {
+ if (DarkFilePaneUIBridge.this.getModel().renameFile(f, f2)) {
+ if (fsv.isParent(chooser.getCurrentDirectory(), f2)) {
+ // The setSelectedFile method produces a new setValueAt invocation while the JTable
+ // is editing. Postpone file selection to be sure that edit mode of the JTable
+ // is completed
+ SwingUtilities.invokeLater(() -> {
+ if (chooser.isMultiSelectionEnabled()) {
+ chooser.setSelectedFiles(new File[]{f2});
+ } else {
+ chooser.setSelectedFile(f2);
+ }
+ });
+ } else {
+ // Could be because of delay in updating Desktop folder
+ // chooser.setSelectedFile(null);
+ }
+ } else {
+ JOptionPane.showMessageDialog(chooser, MessageFormat.format(renameErrorText, oldFileName),
+ renameErrorTitleText, JOptionPane.ERROR_MESSAGE);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ protected Object getFileColumnValue(final File f, final int col) {
+ if (col == COLUMN_SIZE) {
+ return f.isDirectory() ? null : f.length();
+ }
+ return (col == COLUMN_FILENAME)
+ ? f // always return the file itself for the 1st column
+ : ShellFolder.getFolderColumnValue(f, columnMap[col]);
+ }
+
+ public void intervalAdded(final ListDataEvent e) {
+ int i0 = e.getIndex0();
+ int i1 = e.getIndex1();
+ if (i0 == i1) {
+ File file = (File) getModel().getElementAt(i0);
+ if (file.equals(newFolderFile)) {
+ new DelayedSelectionUpdater(file);
+ newFolderFile = null;
+ }
+ }
+
+ fireTableRowsInserted(e.getIndex0(), e.getIndex1());
+ }
+
+ public void intervalRemoved(final ListDataEvent e) {
+ fireTableRowsDeleted(e.getIndex0(), e.getIndex1());
+ }
+
+ public void contentsChanged(final ListDataEvent e) {
+ // Update the selection after the model has been updated
+ new DelayedSelectionUpdater();
+ fireTableDataChanged();
+ }
+
+ public ShellFolderColumnInfo[] getColumns() {
+ return columns;
+ }
+ }
+
+ protected class DetailsTableRowSorter extends TableRowSorter {
+ public DetailsTableRowSorter() {
+ SorterModelWrapper modelWrapper = new SorterModelWrapper();
+ setModelWrapper(modelWrapper);
+ modelWrapper.getModel().addTableModelListener(e -> modelStructureChanged());
+ }
+
+ public void updateComparators(final ShellFolderColumnInfo[] columns) {
+ for (int i = 0; i < columns.length; i++) {
+ Comparator> c = columns[i].getComparator();
+ if (c != null) {
+ c = new DirectoriesFirstComparatorWrapper(i, c);
+ }
+ setComparator(i, c);
+ }
+ }
+
+ @Override
+ public void sort() {
+ ShellFolder.invoke((Callable) () -> {
+ DetailsTableRowSorter.super.sort();
+ return null;
+ });
+ }
+
+ public void modelStructureChanged() {
+ super.modelStructureChanged();
+ updateComparators(detailsTableModel.getColumns());
+ }
+
+ protected class SorterModelWrapper extends ModelWrapper {
+ public TableModel getModel() {
+ return getDetailsTableModel();
+ }
+
+ public int getColumnCount() {
+ return getDetailsTableModel().getColumnCount();
+ }
+
+ public int getRowCount() {
+ return getDetailsTableModel().getRowCount();
+ }
+
+ public Object getValueAt(final int row, final int column) {
+ return DarkFilePaneUIBridge.this.getModel().getElementAt(row);
+ }
+
+ public Integer getIdentifier(final int row) {
+ return row;
+ }
+ }
+ }
+
+ /**
+ * This class sorts directories before files, comparing directory to
+ * directory and file to file using the wrapped comparator.
+ */
+ protected class DirectoriesFirstComparatorWrapper implements Comparator {
+ protected Comparator comparator;
+ protected int column;
+
+ @SuppressWarnings("unchecked")
+ public DirectoriesFirstComparatorWrapper(final int column, final Comparator> comparator) {
+ this.column = column;
+ this.comparator = (Comparator) comparator;
+ }
+
+ public int compare(final File f1, final File f2) {
+ if (f1 != null && f2 != null) {
+ boolean traversable1 = getFileChooser().isTraversable(f1);
+ boolean traversable2 = getFileChooser().isTraversable(f2);
+ // directories go first
+ if (traversable1 && !traversable2) {
+ return -1;
+ }
+ if (!traversable1 && traversable2) {
+ return 1;
+ }
+ }
+ if (detailsTableModel.getColumns()[column].isCompareByColumn()) {
+ return comparator.compare(
+ getDetailsTableModel().getFileColumnValue(f1, column),
+ getDetailsTableModel().getFileColumnValue(f2, column)
+ );
+ }
+ // For this column we need to pass the file itself (not a
+ // column value) to the comparator
+ return comparator.compare(f1, f2);
+ }
+ }
+
+ protected class DetailsTableCellEditor extends DarkTableCellEditor {
+ public DetailsTableCellEditor(final JTextField tf) {
+ super(tf);
+ tf.addFocusListener(editorFocusListener);
+ }
+
+ public Component getTableCellEditorComponent(final JTable table, final Object value,
+ final boolean isSelected, final int row, final int column) {
+ Component comp = super.getTableCellEditorComponent(table, value,
+ isSelected, row, column);
+ if (value instanceof File) {
+ delegate.setValue(getFileChooser().getName((File) value));
+ }
+ if (editorComponent instanceof JTextField) {
+ SwingUtilities.invokeLater(() -> {
+ ((JTextField) editorComponent).selectAll();
+ editorComponent.requestFocus();
+ });
+ }
+
+ return comp;
+ }
+ }
+
+ @SuppressWarnings("serial")
+ // JDK-implementation class
+ class DetailsTableCellRenderer extends DarkTableCellRenderer {
+ JFileChooser chooser;
+ DateFormat df;
+
+ DetailsTableCellRenderer(@NotNull final JFileChooser chooser) {
+ this.chooser = chooser;
+ df = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT,
+ chooser.getLocale());
+ }
+
+ public Component getTableCellRendererComponent(final JTable table, final Object value,
+ boolean isSelected, final boolean hasFocus,
+ final int row, final int column) {
+
+ if ((table.convertColumnIndexToModel(column) != COLUMN_FILENAME ||
+ (listViewWindowsStyle && !table.isFocusOwner())) && !fullRowSelection) {
+ isSelected = false;
+ }
+
+ super.getTableCellRendererComponent(table, value, isSelected,
+ hasFocus, row, column);
+
+ setIcon(null);
+ int modelColumn = table.convertColumnIndexToModel(column);
+ ShellFolderColumnInfo columnInfo = detailsTableModel.getColumns()[modelColumn];
+
+ Integer alignment = columnInfo.getAlignment();
+ if (alignment == null) {
+ alignment = (value instanceof Number)
+ ? SwingConstants.RIGHT
+ : SwingConstants.LEADING;
+ }
+
+ setHorizontalAlignment(alignment);
+
+ // formatting cell text
+ // TODO: it's rather a temporary trick, to be revised
+ String text;
+ if (value == null) {
+ text = "";
+ } else if (value instanceof File) {
+ File file = (File) value;
+ text = chooser.getName(file);
+ Icon icon = chooser.getIcon(file);
+ setIcon(icon);
+
+ } else if (value instanceof Long) {
+ long len = ((Long) value) / 1024L;
+ if (listViewWindowsStyle) {
+ text = MessageFormat.format(kiloByteString, len + 1);
+ } else if (len < 1024L) {
+ text = MessageFormat.format(kiloByteString, (len == 0L) ? 1L : len);
+ } else {
+ len /= 1024L;
+ if (len < 1024L) {
+ text = MessageFormat.format(megaByteString, len);
+ } else {
+ len /= 1024L;
+ text = MessageFormat.format(gigaByteString, len);
+ }
+ }
+
+ } else if (value instanceof Date) {
+ text = df.format((Date) value);
+
+ } else {
+ text = value.toString();
+ }
+
+ setText(text);
+
+ return this;
+ }
+ }
+
+ protected class AlignableTableHeaderRenderer implements TableCellRenderer {
+ TableCellRenderer wrappedRenderer;
+
+ public AlignableTableHeaderRenderer(final TableCellRenderer wrappedRenderer) {
+ this.wrappedRenderer = wrappedRenderer;
+ }
+
+ public Component getTableCellRendererComponent(
+ final JTable table, final Object value, final boolean isSelected,
+ final boolean hasFocus, final int row, final int column) {
+
+ Component c = wrappedRenderer.getTableCellRendererComponent(
+ table, value, isSelected, hasFocus, row, column);
+
+ int modelColumn = table.convertColumnIndexToModel(column);
+ ShellFolderColumnInfo columnInfo = detailsTableModel.getColumns()[modelColumn];
+
+ Integer alignment = columnInfo.getAlignment();
+ if (alignment == null) {
+ alignment = SwingConstants.CENTER;
+ }
+ if (c instanceof JLabel) {
+ ((JLabel) c).setHorizontalAlignment(alignment);
+ }
+
+ return c;
+ }
+ }
+
+ protected class DelayedSelectionUpdater implements Runnable {
+ File editFile;
+
+ DelayedSelectionUpdater() {
+ this(null);
+ }
+
+ DelayedSelectionUpdater(final File editFile) {
+ this.editFile = editFile;
+ if (isShowing()) {
+ SwingUtilities.invokeLater(this);
+ }
+ }
+
+ public void run() {
+ setFileSelected();
+ if (editFile != null) {
+ editFileName(getRowSorter().convertRowIndexToView(
+ getModel().indexOf(editFile)));
+ editFile = null;
+ }
+ }
+ }
+
+ class EditActionListener implements ActionListener {
+ public void actionPerformed(final ActionEvent e) {
+ applyEdit();
+ }
+ }
+
+ @SuppressWarnings("serial") // JDK-implementation class
+ protected class FileRenderer extends DarkListCellRenderer {
+
+ public Component getListCellRendererComponent(@NotNull final JList> list, final Object value,
+ final int index, boolean isSelected,
+ final boolean cellHasFocus) {
+
+ if (listViewWindowsStyle && !list.isFocusOwner()) {
+ isSelected = false;
+ }
+
+ super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
+ File file = (File) value;
+ String fileName = getFileChooser().getName(file);
+ setText(fileName);
+ setFont(list.getFont());
+
+ Icon icon = getFileChooser().getIcon(file);
+ if (icon != null) {
+ setIcon(icon);
+ } else {
+ if (getFileChooser().getFileSystemView().isTraversable(file)) {
+ setText(fileName + File.separator);
+ }
+ }
+
+ return this;
+ }
+ }
+
+ protected class Handler implements MouseListener {
+ protected MouseListener doubleClickListener;
+
+ @SuppressWarnings("deprecation")
+ public void mouseClicked(MouseEvent evt) {
+ JComponent source = (JComponent) evt.getSource();
+
+ int index;
+ if (source instanceof JList) {
+ index = SwingUtilities2.loc2IndexFileList(list, evt.getPoint());
+ } else if (source instanceof JTable) {
+ JTable table = (JTable) source;
+ Point p = evt.getPoint();
+ index = table.rowAtPoint(p);
+
+ boolean pointOutsidePrefSize =
+ SwingUtilities2.pointOutsidePrefSize(
+ table, index, table.columnAtPoint(p), p);
+
+ if (pointOutsidePrefSize && !fullRowSelection) {
+ return;
+ }
+
+ // Translate point from table to list
+ if (index >= 0 && list != null &&
+ listSelectionModel.isSelectedIndex(index)) {
+
+ // Make a new event with the list as source, placing the
+ // click in the corresponding list cell.
+ Rectangle r = list.getCellBounds(index, index);
+ MouseEvent newEvent = new MouseEvent(list, evt.getID(),
+ evt.getWhen(), evt.getModifiers(),
+ r.x + 1, r.y + r.height / 2,
+ evt.getXOnScreen(),
+ evt.getYOnScreen(),
+ evt.getClickCount(), evt.isPopupTrigger(),
+ evt.getButton());
+ AWTAccessor.MouseEventAccessor meAccessor = AWTAccessor.getMouseEventAccessor();
+ meAccessor.setCausedByTouchEvent(newEvent,
+ meAccessor.isCausedByTouchEvent(evt));
+ evt = newEvent;
+ }
+ } else {
+ return;
+ }
+
+ if (index >= 0 && SwingUtilities.isLeftMouseButton(evt)) {
+ JFileChooser fc = getFileChooser();
+
+ // For single click, we handle editing file name
+ if (evt.getClickCount() == 1 && source instanceof JList) {
+ if ((!fc.isMultiSelectionEnabled() || fc.getSelectedFiles().length <= 1)
+ && listSelectionModel.isSelectedIndex(index)
+ && getEditIndex() == index && editFile == null) {
+
+ editFileName(index);
+ } else {
+ setEditIndex(index);
+ }
+ } else if (evt.getClickCount() == 2) {
+ // on double click (open or drill down one directory) be
+ // sure to clear the edit index
+ resetEditIndex();
+ }
+ }
+
+ // Forward event to Basic
+ if (getDoubleClickListener() != null) {
+ getDoubleClickListener().mouseClicked(evt);
+ }
+ }
+
+ public void mousePressed(final MouseEvent evt) {
+ if (evt.getSource() instanceof JList) {
+ // Forward event to Basic
+ if (getDoubleClickListener() != null) {
+ getDoubleClickListener().mousePressed(evt);
+ }
+ }
+ }
+
+ public void mouseReleased(final MouseEvent evt) {
+ if (evt.getSource() instanceof JList) {
+ // Forward event to Basic
+ if (getDoubleClickListener() != null) {
+ getDoubleClickListener().mouseReleased(evt);
+ }
+ }
+ }
+
+ public void mouseEntered(final MouseEvent evt) {
+ JComponent source = (JComponent) evt.getSource();
+ if (source instanceof JTable) {
+ JTable table = (JTable) evt.getSource();
+
+ TransferHandler th1 = getFileChooser().getTransferHandler();
+ TransferHandler th2 = table.getTransferHandler();
+ if (th1 != th2) {
+ table.setTransferHandler(th1);
+ }
+
+ boolean dragEnabled = getFileChooser().getDragEnabled();
+ if (dragEnabled != table.getDragEnabled()) {
+ table.setDragEnabled(dragEnabled);
+ }
+ } else if (source instanceof JList) {
+ // Forward event to Basic
+ if (getDoubleClickListener() != null) {
+ getDoubleClickListener().mouseEntered(evt);
+ }
+ }
+ }
+
+ public void mouseExited(final MouseEvent evt) {
+ if (evt.getSource() instanceof JList) {
+ // Forward event to Basic
+ if (getDoubleClickListener() != null) {
+ getDoubleClickListener().mouseExited(evt);
+ }
+ }
+ }
+
+ protected MouseListener getDoubleClickListener() {
+
+ // Lazy creation of Basic's listener
+ if (doubleClickListener == null && list != null) {
+ doubleClickListener =
+ fileChooserUIAccessor.createDoubleClickListener(list);
+ }
+ return doubleClickListener;
+ }
+ }
+}
diff --git a/src/main/java/com/weis/darklaf/ui/list/DarkListCellFocusBorder.java b/src/main/java/com/weis/darklaf/ui/list/DarkListCellFocusBorder.java
new file mode 100644
index 00000000..2b0e22bf
--- /dev/null
+++ b/src/main/java/com/weis/darklaf/ui/list/DarkListCellFocusBorder.java
@@ -0,0 +1,41 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2019 Jannis Weis
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+package com.weis.darklaf.ui.list;
+
+import com.weis.darklaf.ui.cell.DarkCellBorder;
+import com.weis.darklaf.util.DarkUIUtil;
+
+import javax.swing.*;
+import java.awt.*;
+
+public class DarkListCellFocusBorder extends DarkCellBorder {
+
+ @Override
+ public void paintBorder(final Component c, final Graphics g, final int x, final int y,
+ final int width, final int height) {
+ super.paintBorder(c, g, x, y, width, height);
+ g.setColor(UIManager.getColor("List.focusBorderColor"));
+ DarkUIUtil.drawRect(g, 0, 0, width, height, 1);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/weis/darklaf/ui/list/DarkListCellRenderer.java b/src/main/java/com/weis/darklaf/ui/list/DarkListCellRenderer.java
new file mode 100644
index 00000000..3d7dcc66
--- /dev/null
+++ b/src/main/java/com/weis/darklaf/ui/list/DarkListCellRenderer.java
@@ -0,0 +1,44 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2019 Jannis Weis
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+package com.weis.darklaf.ui.list;
+
+import org.jetbrains.annotations.NotNull;
+
+import javax.swing.*;
+import java.awt.*;
+
+public class DarkListCellRenderer extends DefaultListCellRenderer {
+
+ @Override
+ public Component getListCellRendererComponent(@NotNull final JList> list, final Object value,
+ final int index, final boolean isSelected,
+ final boolean cellHasFocus) {
+ if (Boolean.TRUE.equals(list.getClientProperty("JList.isEditing"))) {
+ if (list.getSelectionModel().getLeadSelectionIndex() == index) {
+ return super.getListCellRendererComponent(list, value, index, false, false);
+ }
+ }
+ return super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
+ }
+}
diff --git a/src/main/java/com/weis/darklaf/ui/list/DarkListUI.java b/src/main/java/com/weis/darklaf/ui/list/DarkListUI.java
index f2d6bf84..08adcd57 100644
--- a/src/main/java/com/weis/darklaf/ui/list/DarkListUI.java
+++ b/src/main/java/com/weis/darklaf/ui/list/DarkListUI.java
@@ -1,20 +1,119 @@
package com.weis.darklaf.ui.list;
+import com.weis.darklaf.util.DarkUIUtil;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import javax.swing.plaf.ComponentUI;
-import javax.swing.plaf.basic.BasicListUI;
+import java.awt.*;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
/**
* @author Jannis Weis
*/
-public class DarkListUI extends BasicListUI {
+public class DarkListUI extends DarkListUIBridge {
@NotNull
@Contract("_ -> new")
public static ComponentUI createUI(final JComponent list) {
return new DarkListUI();
}
+
+ protected void paintCell(final Graphics g, final int row, final Rectangle rowBounds,
+ final ListCellRenderer cellRenderer, final ListModel dataModel,
+ final ListSelectionModel selModel, final int leadIndex) {
+ Object value = dataModel.getElementAt(row);
+ boolean cellHasFocus = list.hasFocus() && (row == leadIndex);
+ boolean isSelected = selModel.isSelectedIndex(row);
+
+ Component rendererComponent =
+ cellRenderer.getListCellRendererComponent(list, value, row, isSelected, cellHasFocus);
+
+ int cx = rowBounds.x;
+ int cy = rowBounds.y;
+ int cw = rowBounds.width;
+ int ch = rowBounds.height;
+
+ if (Boolean.TRUE.equals(list.getClientProperty("JList.shrinkWrap"))) {
+ // Shrink renderer to preferred size. This is mostly used on Windows
+ // where selection is only shown around the file name, instead of
+ // across the whole list cell.
+ int w = Math.min(cw, rendererComponent.getPreferredSize().width + 4);
+ if (!list.getComponentOrientation().isLeftToRight()) {
+ cx += (cw - w);
+ }
+ cw = w;
+ }
+
+ rendererPane.paintComponent(g, rendererComponent, list, cx, cy, cw, ch, true);
+ }
+
+ @Override
+ protected void installListeners() {
+ super.installListeners();
+ list.addMouseListener(new MouseAdapter() {
+
+ });
+ }
+
+ @Override
+ protected Handler getHandler() {
+ if (handler == null) {
+ handler = new DarkHandler();
+ }
+ return handler;
+ }
+
+
+ protected class DarkHandler extends Handler {
+
+ @Override
+ protected void adjustSelection(final MouseEvent e) {
+ int row = list.locationToIndex(e.getPoint());
+ if (row < 0) {
+ // If shift is down in multi-select, we should do nothing.
+ // For single select or non-shift-click, clear the selection
+ if (isFileList && !Boolean.TRUE.equals(list.getClientProperty("JList.fullRowSelection"))
+ && e.getID() == MouseEvent.MOUSE_PRESSED &&
+ (!e.isShiftDown() || list.getSelectionMode() == ListSelectionModel.SINGLE_SELECTION)) {
+ list.clearSelection();
+ }
+ } else {
+ int anchorIndex = adjustIndex(list.getAnchorSelectionIndex(), list);
+ boolean anchorSelected;
+ if (anchorIndex == -1) {
+ anchorIndex = 0;
+ anchorSelected = false;
+ } else {
+ anchorSelected = list.isSelectedIndex(anchorIndex);
+ }
+
+ if (DarkUIUtil.isMenuShortcutKeyDown(e)) {
+ if (e.isShiftDown()) {
+ if (anchorSelected) {
+ list.addSelectionInterval(anchorIndex, row);
+ } else {
+ list.removeSelectionInterval(anchorIndex, row);
+ if (isFileList) {
+ list.addSelectionInterval(row, row);
+ list.getSelectionModel().setAnchorSelectionIndex(anchorIndex);
+ }
+ }
+ } else if (list.isSelectedIndex(row)) {
+ list.removeSelectionInterval(row, row);
+ } else {
+ list.addSelectionInterval(row, row);
+ }
+ } else if (e.isShiftDown()) {
+ list.setSelectionInterval(anchorIndex, row);
+ } else {
+ list.setSelectionInterval(row, row);
+ }
+ }
+ }
+ }
+
+
}
diff --git a/src/main/java/com/weis/darklaf/ui/list/DarkListUIBridge.java b/src/main/java/com/weis/darklaf/ui/list/DarkListUIBridge.java
new file mode 100644
index 00000000..90c73d7e
--- /dev/null
+++ b/src/main/java/com/weis/darklaf/ui/list/DarkListUIBridge.java
@@ -0,0 +1,2257 @@
+package com.weis.darklaf.ui.list;
+
+import com.weis.darklaf.util.DarkUIUtil;
+import com.weis.darklaf.util.LazyActionMap;
+import org.jdesktop.swingx.plaf.basic.core.BasicTransferable;
+import org.jdesktop.swingx.plaf.basic.core.DragRecognitionSupport;
+import sun.swing.DefaultLookup;
+import sun.swing.SwingUtilities2;
+
+import javax.swing.*;
+import javax.swing.event.ListDataEvent;
+import javax.swing.event.ListDataListener;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+import javax.swing.event.MouseInputListener;
+import javax.swing.plaf.ComponentUI;
+import javax.swing.plaf.UIResource;
+import javax.swing.plaf.basic.BasicListUI;
+import javax.swing.text.Position;
+import java.awt.*;
+import java.awt.datatransfer.Transferable;
+import java.awt.event.FocusEvent;
+import java.awt.event.FocusListener;
+import java.awt.event.KeyEvent;
+import java.awt.event.KeyListener;
+import java.awt.event.MouseEvent;
+import java.awt.geom.Point2D;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.util.Objects;
+
+/**
+ * An extensible implementation of {@code ListUI}.
+ *
+ * {@code BasicListUI} instances cannot be shared between multiple
+ * lists.
+ *
+ * @author Hans Muller
+ * @author Philip Milne
+ * @author Shannon Hickey (drag and drop)
+ */
+
+public class DarkListUIBridge extends BasicListUI {
+
+ protected static final StringBuilder BASELINE_COMPONENT_KEY =
+ new StringBuilder("List.baselineComponent");
+ /**
+ * The bit relates to model changed property.
+ */
+ protected static final int modelChanged = 1 << 0;
+ /**
+ * The bit relates to selection model changed property.
+ */
+ protected static final int selectionModelChanged = 1 << 1;
+
+ // Listeners that this UI attaches to the JList
+ /**
+ * The bit relates to font changed property.
+ */
+ protected static final int fontChanged = 1 << 2;
+ /**
+ * The bit relates to fixed cell width changed property.
+ */
+ protected static final int fixedCellWidthChanged = 1 << 3;
+ /**
+ * The bit relates to fixed cell height changed property.
+ */
+ protected static final int fixedCellHeightChanged = 1 << 4;
+ /**
+ * The bit relates to prototype cell value changed property.
+ */
+ protected static final int prototypeCellValueChanged = 1 << 5;
+ /**
+ * The bit relates to cell renderer changed property.
+ */
+ protected static final int cellRendererChanged = 1 << 6;
+ protected static final int layoutOrientationChanged = 1 << 7;
+ protected static final int heightChanged = 1 << 8;
+ protected static final int widthChanged = 1 << 9;
+ protected static final int componentOrientationChanged = 1 << 10;
+ protected static final int DROP_LINE_THICKNESS = 2;
+ /**
+ * Used by IncrementLeadSelectionAction. Indicates the action should
+ * change the lead, and not select it.
+ */
+ protected static final int CHANGE_LEAD = 0;
+ /**
+ * Used by IncrementLeadSelectionAction. Indicates the action should
+ * change the selection and lead.
+ */
+ protected static final int CHANGE_SELECTION = 1;
+ /**
+ * Used by IncrementLeadSelectionAction. Indicates the action should
+ * extend the selection from the anchor to the next index.
+ */
+ protected static final int EXTEND_SELECTION = 2;
+
+ // Following ivars are used if the list is laying out horizontally
+ protected static final TransferHandler defaultTransferHandler = new ListTransferHandler();
+ /**
+ * The instance of {@code JList}.
+ */
+ protected JList list = null;
+ /**
+ * The instance of {@code CellRendererPane}.
+ */
+ protected CellRendererPane rendererPane;
+ /**
+ * {@code FocusListener} that attached to {@code JList}.
+ */
+ protected FocusListener focusListener;
+ /**
+ * {@code MouseInputListener} that attached to {@code JList}.
+ */
+ protected MouseInputListener mouseInputListener;
+ /**
+ * {@code ListSelectionListener} that attached to {@code JList}.
+ */
+ protected ListSelectionListener listSelectionListener;
+
+ /* The bits below define JList property changes that affect layout.
+ * When one of these properties changes we set a bit in
+ * updateLayoutStateNeeded. The change is dealt with lazily, see
+ * maybeUpdateLayoutState. Changes to the JLists model, e.g. the
+ * models length changed, are handled similarly, see DataListener.
+ */
+ /**
+ * {@code ListDataListener} that attached to {@code JList}.
+ */
+ protected ListDataListener listDataListener;
+ /**
+ * {@code PropertyChangeListener} that attached to {@code JList}.
+ */
+ protected PropertyChangeListener propertyChangeListener;
+ protected Handler handler;
+ /**
+ * The array of cells' height
+ */
+ protected int[] cellHeights = null;
+ /**
+ * The height of cell.
+ */
+ protected int cellHeight = -1;
+ /**
+ * The width of cell.
+ */
+ protected int cellWidth = -1;
+ /**
+ * The value represents changes to {@code JList} model.
+ */
+ protected int updateLayoutStateNeeded = modelChanged;
+ /**
+ * Height of the list. When asked to paint, if the current size of
+ * the list differs, this will update the layout state.
+ */
+ protected int listHeight;
+ /**
+ * Width of the list. When asked to paint, if the current size of
+ * the list differs, this will update the layout state.
+ */
+ protected int listWidth;
+ /**
+ * The layout orientation of the list.
+ */
+ protected int layoutOrientation;
+ /**
+ * Number of columns to create.
+ */
+ protected int columnCount;
+ /**
+ * Preferred height to make the list, this is only used if the
+ * the list is layed out horizontally.
+ */
+ protected int preferredHeight;
+ /**
+ * Number of rows per column. This is only used if the row height is
+ * fixed.
+ */
+ protected int rowsPerColumn;
+ /**
+ * The time factor to treate the series of typed alphanumeric key
+ * as prefix for first letter navigation.
+ */
+ protected long timeFactor = 1000L;
+ /**
+ * Local cache of JList's client property "List.isFileList"
+ */
+ protected boolean isFileList = false;
+ /**
+ * Local cache of JList's component orientation property
+ */
+ protected boolean isLeftToRight = true;
+
+ /**
+ * Returns a new instance of {@code BasicListUI}.
+ * {@code BasicListUI} delegates are allocated one per {@code JList}.
+ *
+ * @param list a component
+ * @return a new {@code ListUI} implementation for the Windows look and feel.
+ */
+ public static ComponentUI createUI(final JComponent list) {
+ return new BasicListUI();
+ }
+
+ protected static int adjustIndex(final int index, final JList> list) {
+ return index < list.getModel().getSize() ? index : -1;
+ }
+
+ /**
+ * Paint one List cell: compute the relevant state, get the "rubber stamp"
+ * cell renderer component, and then use the {@code CellRendererPane} to paint it.
+ * Subclasses may want to override this method rather than {@code paint()}.
+ *
+ * @param g an instance of {@code Graphics}
+ * @param row a row
+ * @param rowBounds a bounding rectangle to render to
+ * @param cellRenderer a list of {@code ListCellRenderer}
+ * @param dataModel a list model
+ * @param selModel a selection model
+ * @param leadIndex a lead index
+ * @see #paint
+ */
+ protected void paintCell(
+ final Graphics g,
+ final int row,
+ final Rectangle rowBounds,
+ final ListCellRenderer cellRenderer,
+ final ListModel dataModel,
+ final ListSelectionModel selModel,
+ final int leadIndex) {
+ Object value = dataModel.getElementAt(row);
+ boolean cellHasFocus = list.hasFocus() && (row == leadIndex);
+ boolean isSelected = selModel.isSelectedIndex(row);
+
+ Component rendererComponent =
+ cellRenderer.getListCellRendererComponent(list, value, row, isSelected, cellHasFocus);
+
+ int cx = rowBounds.x;
+ int cy = rowBounds.y;
+ int cw = rowBounds.width;
+ int ch = rowBounds.height;
+
+ if (isFileList) {
+ // Shrink renderer to preferred size. This is mostly used on Windows
+ // where selection is only shown around the file name, instead of
+ // across the whole list cell.
+ int w = Math.min(cw, rendererComponent.getPreferredSize().width + 4);
+ if (!isLeftToRight) {
+ cx += (cw - w);
+ }
+ cw = w;
+ }
+
+ rendererPane.paintComponent(g, rendererComponent, list, cx, cy, cw, ch, true);
+ }
+
+ /**
+ * Paint the rows that intersect the Graphics objects clipRect. This
+ * method calls paintCell as necessary. Subclasses
+ * may want to override these methods.
+ *
+ * @see #paintCell
+ */
+ public void paint(final Graphics g, final JComponent c) {
+ Shape clip = g.getClip();
+ paintImpl(g, c);
+ g.setClip(clip);
+
+ paintDropLine(g);
+ }
+
+ /**
+ * Returns the baseline.
+ *
+ * @throws NullPointerException {@inheritDoc}
+ * @throws IllegalArgumentException {@inheritDoc}
+ * @see javax.swing.JComponent#getBaseline(int, int)
+ * @since 1.6
+ */
+ public int getBaseline(final JComponent c, final int width, final int height) {
+ super.getBaseline(c, width, height);
+ int rowHeight = list.getFixedCellHeight();
+ UIDefaults lafDefaults = UIManager.getLookAndFeelDefaults();
+ Component renderer = (Component) lafDefaults.get(
+ BASELINE_COMPONENT_KEY);
+ if (renderer == null) {
+ @SuppressWarnings("unchecked")
+ ListCellRenderer lcr = (ListCellRenderer) UIManager.get(
+ "List.cellRenderer");
+
+ // fix for 6711072 some LAFs like Nimbus do not provide this
+ // UIManager key and we should not through a NPE here because of it
+ if (lcr == null) {
+ lcr = new DefaultListCellRenderer();
+ }
+ renderer = lcr.getListCellRendererComponent(
+ list, "a", -1, false, false);
+ lafDefaults.put(BASELINE_COMPONENT_KEY, renderer);
+ }
+ renderer.setFont(list.getFont());
+ // JList actually has much more complex behavior here.
+ // If rowHeight != -1 the rowHeight is either the max of all cell
+ // heights (layout orientation != VERTICAL), or is variable depending
+ // upon the cell. We assume a default size.
+ // We could theoretically query the real renderer, but that would
+ // not work for an empty model and the results may vary with
+ // the content.
+ if (rowHeight == -1) {
+ rowHeight = renderer.getPreferredSize().height;
+ }
+ return renderer.getBaseline(Integer.MAX_VALUE, rowHeight) +
+ list.getInsets().top;
+ }
+
+ /**
+ * Returns an enum indicating how the baseline of the component
+ * changes as the size changes.
+ *
+ * @throws NullPointerException {@inheritDoc}
+ * @see javax.swing.JComponent#getBaseline(int, int)
+ * @since 1.6
+ */
+ public Component.BaselineResizeBehavior getBaselineResizeBehavior(
+ final JComponent c) {
+ super.getBaselineResizeBehavior(c);
+ return Component.BaselineResizeBehavior.CONSTANT_ASCENT;
+ }
+
+ /**
+ * The preferredSize of the list depends upon the layout orientation.
+ *
+ *
+ * Describes the preferred size for each layout orientation
+ *
+ *
+ *
+ * Layout Orientation
+ * Preferred Size
+ *
+ *
+ *
+ * JList.VERTICAL
+ * The preferredSize of the list is total height of the rows
+ * and the maximum width of the cells. If JList.fixedCellHeight
+ * is specified then the total height of the rows is just
+ * (cellVerticalMargins + fixedCellHeight) * model.getSize() where
+ * rowVerticalMargins is the space we allocate for drawing
+ * the yellow focus outline. Similarly if fixedCellWidth is
+ * specified then we just use that.
+ *
+ * JList.VERTICAL_WRAP
+ * If the visible row count is greater than zero, the preferredHeight
+ * is the maximum cell height * visibleRowCount. If the visible row
+ * count is <= 0, the preferred height is either the current height
+ * of the list, or the maximum cell height, whichever is
+ * bigger. The preferred width is than the maximum cell width *
+ * number of columns needed. Where the number of columns needs is
+ * list.height / max cell height. Max cell height is either the fixed
+ * cell height, or is determined by iterating through all the cells
+ * to find the maximum height from the ListCellRenderer.
+ *
+ * JList.HORIZONTAL_WRAP
+ * If the visible row count is greater than zero, the preferredHeight
+ * is the maximum cell height * adjustedRowCount. Where
+ * visibleRowCount is used to determine the number of columns.
+ * Because this lays out horizontally the number of rows is
+ * then determined from the column count. For example, lets say
+ * you have a model with 10 items and the visible row count is 8.
+ * The number of columns needed to display this is 2, but you no
+ * longer need 8 rows to display this, you only need 5, thus
+ * the adjustedRowCount is 5.
+ *
+ * If the visible row count is <= 0, the preferred height is dictated
+ * by the number of columns, which will be as many as can fit in the
+ * width of the {@code JList} (width / max cell width), with at least
+ * one column. The preferred height then becomes the model size / number
+ * of columns * maximum cell height. Max cell height is either the fixed
+ * cell height, or is determined by iterating through all the cells to
+ * find the maximum height from the ListCellRenderer.
+ *
+ *
+ *
+ * The above specifies the raw preferred width and height. The resulting
+ * preferred width is the above width + insets.left + insets.right and
+ * the resulting preferred height is the above height + insets.top +
+ * insets.bottom. Where the Insets
are determined from
+ * list.getInsets()
.
+ *
+ * @param c The JList component.
+ * @return The total size of the list.
+ */
+ public Dimension getPreferredSize(final JComponent c) {
+ maybeUpdateLayoutState();
+
+ int lastRow = list.getModel().getSize() - 1;
+ if (lastRow < 0) {
+ return new Dimension(0, 0);
+ }
+
+ Insets insets = list.getInsets();
+ int width = cellWidth * columnCount + insets.left + insets.right;
+ int height;
+
+ if (layoutOrientation != JList.VERTICAL) {
+ height = preferredHeight;
+ } else {
+ Rectangle bounds = getCellBounds(list, lastRow);
+
+ if (bounds != null) {
+ height = bounds.y + bounds.height + insets.bottom;
+ } else {
+ height = 0;
+ }
+ }
+ return new Dimension(width, height);
+ }
+
+ /**
+ * Selected the previous row and force it to be visible.
+ *
+ * @see JList#ensureIndexIsVisible
+ */
+ protected void selectPreviousIndex() {
+ int s = list.getSelectedIndex();
+ if (s > 0) {
+ s -= 1;
+ list.setSelectedIndex(s);
+ list.ensureIndexIsVisible(s);
+ }
+ }
+
+ /**
+ * Selected the previous row and force it to be visible.
+ *
+ * @see JList#ensureIndexIsVisible
+ */
+ protected void selectNextIndex() {
+ int s = list.getSelectedIndex();
+ if ((s + 1) < list.getModel().getSize()) {
+ s += 1;
+ list.setSelectedIndex(s);
+ list.ensureIndexIsVisible(s);
+ }
+ }
+
+ /**
+ * Registers the keyboard bindings on the JList
that the
+ * BasicListUI
is associated with. This method is called at
+ * installUI() time.
+ *
+ * @see #installUI
+ */
+ protected void installKeyboardActions() {
+ InputMap inputMap = getInputMap(JComponent.WHEN_FOCUSED);
+ SwingUtilities.replaceUIInputMap(list, JComponent.WHEN_FOCUSED, inputMap);
+ LazyActionMap.installLazyActionMap(list, BasicListUI.class, "List.actionMap");
+ }
+
+ /**
+ * Unregisters keyboard actions installed from
+ * installKeyboardActions
.
+ * This method is called at uninstallUI() time - subclassess should
+ * ensure that all of the keyboard actions registered at installUI
+ * time are removed here.
+ *
+ * @see #installUI
+ */
+ protected void uninstallKeyboardActions() {
+ SwingUtilities.replaceUIActionMap(list, null);
+ SwingUtilities.replaceUIInputMap(list, JComponent.WHEN_FOCUSED, null);
+ }
+
+ /**
+ * Creates and installs the listeners for the JList, its model, and its
+ * selectionModel. This method is called at installUI() time.
+ *
+ * @see #installUI
+ * @see #uninstallListeners
+ */
+ protected void installListeners() {
+ TransferHandler th = list.getTransferHandler();
+ if (th == null || th instanceof UIResource) {
+ list.setTransferHandler(defaultTransferHandler);
+ // default TransferHandler doesn't support drop
+ // so we don't want drop handling
+ if (list.getDropTarget() instanceof UIResource) {
+ list.setDropTarget(null);
+ }
+ }
+
+ focusListener = createFocusListener();
+ mouseInputListener = createMouseInputListener();
+ propertyChangeListener = createPropertyChangeListener();
+ listSelectionListener = createListSelectionListener();
+ listDataListener = createListDataListener();
+
+ list.addFocusListener(focusListener);
+ list.addMouseListener(mouseInputListener);
+ list.addMouseMotionListener(mouseInputListener);
+ list.addPropertyChangeListener(propertyChangeListener);
+ list.addKeyListener(getHandler());
+
+ ListModel model = list.getModel();
+ if (model != null) {
+ model.addListDataListener(listDataListener);
+ }
+
+ ListSelectionModel selectionModel = list.getSelectionModel();
+ if (selectionModel != null) {
+ selectionModel.addListSelectionListener(listSelectionListener);
+ }
+ }
+
+ /**
+ * Removes the listeners from the JList, its model, and its
+ * selectionModel. All of the listener fields, are reset to
+ * null here. This method is called at uninstallUI() time,
+ * it should be kept in sync with installListeners.
+ *
+ * @see #uninstallUI
+ * @see #installListeners
+ */
+ protected void uninstallListeners() {
+ list.removeFocusListener(focusListener);
+ list.removeMouseListener(mouseInputListener);
+ list.removeMouseMotionListener(mouseInputListener);
+ list.removePropertyChangeListener(propertyChangeListener);
+ list.removeKeyListener(getHandler());
+
+ ListModel model = list.getModel();
+ if (model != null) {
+ model.removeListDataListener(listDataListener);
+ }
+
+ ListSelectionModel selectionModel = list.getSelectionModel();
+ if (selectionModel != null) {
+ selectionModel.removeListSelectionListener(listSelectionListener);
+ }
+
+ focusListener = null;
+ mouseInputListener = null;
+ listSelectionListener = null;
+ listDataListener = null;
+ propertyChangeListener = null;
+ handler = null;
+ }
+
+ /**
+ * Initializes list properties such as font, foreground, and background,
+ * and adds the CellRendererPane. The font, foreground, and background
+ * properties are only set if their current value is either null
+ * or a UIResource, other properties are set if the current
+ * value is null.
+ *
+ * @see #uninstallDefaults
+ * @see #installUI
+ * @see CellRendererPane
+ */
+ protected void installDefaults() {
+ list.setLayout(null);
+
+ LookAndFeel.installBorder(list, "List.border");
+
+ LookAndFeel.installColorsAndFont(list, "List.background", "List.foreground", "List.font");
+
+ LookAndFeel.installProperty(list, "opaque", Boolean.TRUE);
+
+ if (list.getCellRenderer() == null) {
+ @SuppressWarnings("unchecked")
+ ListCellRenderer tmp = (ListCellRenderer) (UIManager.get("List.cellRenderer"));
+ list.setCellRenderer(tmp);
+ }
+
+ Color sbg = list.getSelectionBackground();
+ if (sbg == null || sbg instanceof UIResource) {
+ list.setSelectionBackground(UIManager.getColor("List.selectionBackground"));
+ }
+
+ Color sfg = list.getSelectionForeground();
+ if (sfg == null || sfg instanceof UIResource) {
+ list.setSelectionForeground(UIManager.getColor("List.selectionForeground"));
+ }
+
+ Long l = (Long) UIManager.get("List.timeFactor");
+ timeFactor = (l != null) ? l : 1000L;
+
+ updateIsFileList();
+ }
+
+ /**
+ * Sets the list properties that have not been explicitly overridden to
+ * {@code null}. A property is considered overridden if its current value
+ * is not a {@code UIResource}.
+ *
+ * @see #installDefaults
+ * @see #uninstallUI
+ * @see CellRendererPane
+ */
+ protected void uninstallDefaults() {
+ LookAndFeel.uninstallBorder(list);
+ if (list.getFont() instanceof UIResource) {
+ list.setFont(null);
+ }
+ if (list.getForeground() instanceof UIResource) {
+ list.setForeground(null);
+ }
+ if (list.getBackground() instanceof UIResource) {
+ list.setBackground(null);
+ }
+ if (list.getSelectionBackground() instanceof UIResource) {
+ list.setSelectionBackground(null);
+ }
+ if (list.getSelectionForeground() instanceof UIResource) {
+ list.setSelectionForeground(null);
+ }
+ if (list.getCellRenderer() instanceof UIResource) {
+ list.setCellRenderer(null);
+ }
+ if (list.getTransferHandler() instanceof UIResource) {
+ list.setTransferHandler(null);
+ }
+ }
+
+ /**
+ * Initializes this.list
by calling installDefaults()
,
+ * installListeners()
, and installKeyboardActions()
+ * in order.
+ *
+ * @see #installDefaults
+ * @see #installListeners
+ * @see #installKeyboardActions
+ */
+ public void installUI(final JComponent c) {
+ @SuppressWarnings("unchecked")
+ JList tmp = (JList) c;
+ list = tmp;
+
+ layoutOrientation = list.getLayoutOrientation();
+
+ rendererPane = new CellRendererPane();
+ list.add(rendererPane);
+
+ columnCount = 1;
+
+ updateLayoutStateNeeded = modelChanged;
+ isLeftToRight = list.getComponentOrientation().isLeftToRight();
+
+ installDefaults();
+ installListeners();
+ installKeyboardActions();
+ }
+
+ protected void updateIsFileList() {
+ boolean b = Boolean.TRUE.equals(list.getClientProperty("List.isFileList"));
+ if (b != isFileList) {
+ isFileList = b;
+ Font oldFont = list.getFont();
+ if (oldFont == null || oldFont instanceof UIResource) {
+ Font newFont = UIManager.getFont(b ? "FileChooser.listFont" : "List.font");
+ if (newFont != null && newFont != oldFont) {
+ list.setFont(newFont);
+ }
+ }
+ }
+ }
+
+ protected Handler getHandler() {
+ if (handler == null) {
+ handler = new Handler();
+ }
+ return handler;
+ }
+
+ InputMap getInputMap(final int condition) {
+ if (condition == JComponent.WHEN_FOCUSED) {
+ InputMap keyMap = (InputMap) DefaultLookup.get(
+ list, this, "List.focusInputMap");
+ InputMap rtlKeyMap;
+
+ if (isLeftToRight ||
+ ((rtlKeyMap = (InputMap) DefaultLookup.get(list, this,
+ "List.focusInputMap.RightToLeft")) == null)) {
+ return keyMap;
+ } else {
+ rtlKeyMap.setParent(keyMap);
+ return rtlKeyMap;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Uninitializes this.list
by calling uninstallListeners()
,
+ * uninstallKeyboardActions()
, and uninstallDefaults()
+ * in order. Sets this.list to null.
+ *
+ * @see #uninstallListeners
+ * @see #uninstallKeyboardActions
+ * @see #uninstallDefaults
+ */
+ public void uninstallUI(final JComponent c) {
+ uninstallListeners();
+ uninstallDefaults();
+ uninstallKeyboardActions();
+
+ cellWidth = cellHeight = -1;
+ cellHeights = null;
+
+ listWidth = listHeight = -1;
+
+ list.remove(rendererPane);
+ rendererPane = null;
+ list = null;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @throws NullPointerException {@inheritDoc}
+ */
+ public int locationToIndex(final JList> list, final Point location) {
+ maybeUpdateLayoutState();
+ return convertLocationToModel(location.x, location.y);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Point indexToLocation(final JList> list, final int index) {
+ maybeUpdateLayoutState();
+ Rectangle rect = getCellBounds(list, index, index);
+
+ if (rect != null) {
+ return new Point(rect.x, rect.y);
+ }
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Rectangle getCellBounds(final JList> list, final int index1, final int index2) {
+ maybeUpdateLayoutState();
+
+ int minIndex = Math.min(index1, index2);
+ int maxIndex = Math.max(index1, index2);
+
+ if (minIndex >= list.getModel().getSize()) {
+ return null;
+ }
+
+ Rectangle minBounds = getCellBounds(list, minIndex);
+
+ if (minBounds == null) {
+ return null;
+ }
+ if (minIndex == maxIndex) {
+ return minBounds;
+ }
+ Rectangle maxBounds = getCellBounds(list, maxIndex);
+
+ if (maxBounds != null) {
+ if (layoutOrientation == JList.HORIZONTAL_WRAP) {
+ int minRow = convertModelToRow(minIndex);
+ int maxRow = convertModelToRow(maxIndex);
+
+ if (minRow != maxRow) {
+ minBounds.x = 0;
+ minBounds.width = list.getWidth();
+ }
+ } else if (minBounds.x != maxBounds.x) {
+ // Different columns
+ minBounds.y = 0;
+ minBounds.height = list.getHeight();
+ }
+ minBounds.add(maxBounds);
+ }
+ return minBounds;
+ }
+
+ /**
+ * Returns the height of the specified row based on the current layout.
+ *
+ * @param row a row
+ * @return the specified row height or -1 if row isn't valid
+ * @see #convertYToRow
+ * @see #convertRowToY
+ * @see #updateLayoutState
+ */
+ protected int getRowHeight(final int row) {
+ return getHeight(0, row);
+ }
+
+ /**
+ * Convert the {@code JList} relative coordinate to the row that contains it,
+ * based on the current layout. If {@code y0} doesn't fall within any row,
+ * return -1.
+ *
+ * @param y0 a relative Y coordinate
+ * @return the row that contains y0, or -1
+ * @see #getRowHeight
+ * @see #updateLayoutState
+ */
+ protected int convertYToRow(final int y0) {
+ return convertLocationToRow(0, y0, false);
+ }
+
+ /**
+ * Return the {@code JList} relative Y coordinate of the origin of the specified
+ * row or -1 if row isn't valid.
+ *
+ * @param row a row
+ * @return the Y coordinate of the origin of row, or -1
+ * @see #getRowHeight
+ * @see #updateLayoutState
+ */
+ protected int convertRowToY(final int row) {
+ if (row >= getRowCount(0) || row < 0) {
+ return -1;
+ }
+ Rectangle bounds = getCellBounds(list, row, row);
+ return bounds.y;
+ }
+
+ /**
+ * If updateLayoutStateNeeded is non zero, call updateLayoutState() and reset
+ * updateLayoutStateNeeded. This method should be called by methods
+ * before doing any computation based on the geometry of the list.
+ * For example it's the first call in paint() and getPreferredSize().
+ *
+ * @see #updateLayoutState
+ */
+ protected void maybeUpdateLayoutState() {
+ if (updateLayoutStateNeeded != 0) {
+ updateLayoutState();
+ updateLayoutStateNeeded = 0;
+ }
+ }
+
+ /**
+ * Recompute the value of cellHeight or cellHeights based
+ * and cellWidth, based on the current font and the current
+ * values of fixedCellWidth, fixedCellHeight, and prototypeCellValue.
+ *
+ * @see #maybeUpdateLayoutState
+ */
+ protected void updateLayoutState() {
+ /* If both JList fixedCellWidth and fixedCellHeight have been
+ * set, then initialize cellWidth and cellHeight, and set
+ * cellHeights to null.
+ */
+
+ int fixedCellHeight = list.getFixedCellHeight();
+ int fixedCellWidth = list.getFixedCellWidth();
+
+ cellWidth = fixedCellWidth;
+
+ if (fixedCellHeight != -1) {
+ cellHeight = fixedCellHeight;
+ cellHeights = null;
+ } else {
+ cellHeight = -1;
+ cellHeights = new int[list.getModel().getSize()];
+ }
+
+ /* If either of JList fixedCellWidth and fixedCellHeight haven't
+ * been set, then initialize cellWidth and cellHeights by
+ * scanning through the entire model. Note: if the renderer is
+ * null, we just set cellWidth and cellHeights[*] to zero,
+ * if they're not set already.
+ */
+
+ if ((fixedCellWidth == -1) || (fixedCellHeight == -1)) {
+
+ ListModel dataModel = list.getModel();
+ int dataModelSize = dataModel.getSize();
+ ListCellRenderer renderer = list.getCellRenderer();
+
+ if (renderer != null) {
+ for (int index = 0; index < dataModelSize; index++) {
+ Object value = dataModel.getElementAt(index);
+ Component c = renderer.getListCellRendererComponent(list, value, index, false, false);
+ rendererPane.add(c);
+ Dimension cellSize = c.getPreferredSize();
+ if (fixedCellWidth == -1) {
+ cellWidth = Math.max(cellSize.width, cellWidth);
+ }
+ if (fixedCellHeight == -1) {
+ cellHeights[index] = cellSize.height;
+ }
+ }
+ } else {
+ if (cellWidth == -1) {
+ cellWidth = 0;
+ }
+ if (cellHeights == null) {
+ cellHeights = new int[dataModelSize];
+ }
+ for (int index = 0; index < dataModelSize; index++) {
+ cellHeights[index] = 0;
+ }
+ }
+ }
+
+ columnCount = 1;
+ if (layoutOrientation != JList.VERTICAL) {
+ updateHorizontalLayoutState(fixedCellWidth, fixedCellHeight);
+ }
+ }
+
+ /**
+ * Creates a delegate that implements {@code MouseInputListener}.
+ * The delegate is added to the corresponding {@code java.awt.Component} listener
+ * lists at {@code installUI()} time. Subclasses can override this method to return
+ * a custom {@code MouseInputListener}, e.g.
+ *
+ * class MyListUI extends BasicListUI {
+ * protected MouseInputListener createMouseInputListener () {
+ * return new MyMouseInputHandler();
+ * }
+ * public class MyMouseInputHandler extends MouseInputHandler {
+ * public void mouseMoved(MouseEvent e) {
+ * // do some extra work when the mouse moves
+ * super.mouseMoved(e);
+ * }
+ * }
+ * }
+ *
+ *
+ * @return an instance of {@code MouseInputListener}
+ * @see MouseInputHandler
+ * @see #installUI
+ */
+ protected MouseInputListener createMouseInputListener() {
+ return getHandler();
+ }
+
+ /**
+ * Returns an instance of {@code FocusListener}.
+ *
+ * @return an instance of {@code FocusListener}
+ */
+ protected FocusListener createFocusListener() {
+ return getHandler();
+ }
+
+ /**
+ * Creates an instance of {@code ListSelectionHandler} that's added to
+ * the {@code JLists} by selectionModel as needed. Subclasses can override
+ * this method to return a custom {@code ListSelectionListener}, e.g.
+ *
+ * class MyListUI extends BasicListUI {
+ * protected ListSelectionListener createListSelectionListener () {
+ * return new MySelectionListener();
+ * }
+ * public class MySelectionListener extends ListSelectionHandler {
+ * public void valueChanged(ListSelectionEvent e) {
+ * // do some extra work when the selection changes
+ * super.valueChange(e);
+ * }
+ * }
+ * }
+ *
+ *
+ * @return an instance of {@code ListSelectionHandler}
+ * @see ListSelectionHandler
+ * @see #installUI
+ */
+ protected ListSelectionListener createListSelectionListener() {
+ return getHandler();
+ }
+
+ /**
+ * Creates an instance of {@code ListDataListener} that's added to
+ * the {@code JLists} by model as needed. Subclasses can override
+ * this method to return a custom {@code ListDataListener}, e.g.
+ *
+ * class MyListUI extends BasicListUI {
+ * protected ListDataListener createListDataListener () {
+ * return new MyListDataListener();
+ * }
+ * public class MyListDataListener extends ListDataHandler {
+ * public void contentsChanged(ListDataEvent e) {
+ * // do some extra work when the models contents change
+ * super.contentsChange(e);
+ * }
+ * }
+ * }
+ *
+ *
+ * @return an instance of {@code ListDataListener}
+ * @see ListDataListener
+ * @see JList#getModel
+ * @see #installUI
+ */
+ protected ListDataListener createListDataListener() {
+ return getHandler();
+ }
+
+ /**
+ * Creates an instance of {@code PropertyChangeHandler} that's added to
+ * the {@code JList} by {@code installUI()}. Subclasses can override this method
+ * to return a custom {@code PropertyChangeListener}, e.g.
+ *
+ * class MyListUI extends BasicListUI {
+ * protected PropertyChangeListener createPropertyChangeListener () {
+ * return new MyPropertyChangeListener();
+ * }
+ * public class MyPropertyChangeListener extends PropertyChangeHandler {
+ * public void propertyChange(PropertyChangeEvent e) {
+ * if (e.getPropertyName().equals("model")) {
+ * // do some extra work when the model changes
+ * }
+ * super.propertyChange(e);
+ * }
+ * }
+ * }
+ *
+ *
+ * @return an instance of {@code PropertyChangeHandler}
+ * @see PropertyChangeListener
+ * @see #installUI
+ */
+ protected PropertyChangeListener createPropertyChangeListener() {
+ return getHandler();
+ }
+
+ /**
+ * Returns the closest location to the model index of the passed in
+ * location.
+ */
+ protected int convertLocationToModel(final int x, final int y) {
+ int row = convertLocationToRow(x, y, true);
+ int column = convertLocationToColumn(x, y);
+
+ if (row >= 0 && column >= 0) {
+ return getModelIndex(column, row);
+ }
+ return -1;
+ }
+
+ /**
+ * Returns the row at location x/y.
+ *
+ * @param closest If true and the location doesn't exactly match a
+ * particular location, this will return the closest row.
+ */
+ protected int convertLocationToRow(final int x, final int y0, final boolean closest) {
+ int size = list.getModel().getSize();
+
+ if (size <= 0) {
+ return -1;
+ }
+ Insets insets = list.getInsets();
+ if (cellHeights == null) {
+ int row = (cellHeight == 0) ? 0 :
+ ((y0 - insets.top) / cellHeight);
+ if (closest) {
+ if (row < 0) {
+ row = 0;
+ } else if (row >= size) {
+ row = size - 1;
+ }
+ }
+ return row;
+ } else if (size > cellHeights.length) {
+ return -1;
+ } else {
+ int y = insets.top;
+ int row = 0;
+
+ if (closest && y0 < y) {
+ return 0;
+ }
+ int i;
+ for (i = 0; i < size; i++) {
+ if ((y0 >= y) && (y0 < y + cellHeights[i])) {
+ return row;
+ }
+ y += cellHeights[i];
+ row += 1;
+ }
+ return i - 1;
+ }
+ }
+
+ /**
+ * Returns the closest column to the passed in location.
+ */
+ protected int convertLocationToColumn(final int x, final int y) {
+ if (cellWidth > 0) {
+ if (layoutOrientation == JList.VERTICAL) {
+ return 0;
+ }
+ Insets insets = list.getInsets();
+ int col;
+ if (isLeftToRight) {
+ col = (x - insets.left) / cellWidth;
+ } else {
+ col = (list.getWidth() - x - insets.right - 1) / cellWidth;
+ }
+ if (col < 0) {
+ return 0;
+ } else if (col >= columnCount) {
+ return columnCount - 1;
+ }
+ return col;
+ }
+ return 0;
+ }
+
+ /**
+ * Returns the model index for the specified display location.
+ * If column
xrow
is beyond the length of the
+ * model, this will return the model size - 1.
+ */
+ protected int getModelIndex(final int column, final int row) {
+ switch (layoutOrientation) {
+ case JList.VERTICAL_WRAP:
+ return Math.min(list.getModel().getSize() - 1, rowsPerColumn *
+ column + Math.min(row, rowsPerColumn - 1));
+ case JList.HORIZONTAL_WRAP:
+ return Math.min(list.getModel().getSize() - 1, row * columnCount +
+ column);
+ default:
+ return row;
+ }
+ }
+
+ /**
+ * Invoked when the list is layed out horizontally to determine how
+ * many columns to create.
+ *
+ * This updates the rowsPerColumn,
columnCount
,
+ * preferredHeight
and potentially cellHeight
+ * instance variables.
+ */
+ protected void updateHorizontalLayoutState(final int fixedCellWidth,
+ final int fixedCellHeight) {
+ int visRows = list.getVisibleRowCount();
+ int dataModelSize = list.getModel().getSize();
+ Insets insets = list.getInsets();
+
+ listHeight = list.getHeight();
+ listWidth = list.getWidth();
+
+ if (dataModelSize == 0) {
+ rowsPerColumn = columnCount = 0;
+ preferredHeight = insets.top + insets.bottom;
+ return;
+ }
+
+ int height;
+
+ if (fixedCellHeight != -1) {
+ height = fixedCellHeight;
+ } else {
+ // Determine the max of the renderer heights.
+ int maxHeight = 0;
+ if (cellHeights.length > 0) {
+ maxHeight = cellHeights[cellHeights.length - 1];
+ for (int counter = cellHeights.length - 2;
+ counter >= 0; counter--) {
+ maxHeight = Math.max(maxHeight, cellHeights[counter]);
+ }
+ }
+ height = cellHeight = maxHeight;
+ cellHeights = null;
+ }
+ // The number of rows is either determined by the visible row
+ // count, or by the height of the list.
+ rowsPerColumn = dataModelSize;
+ if (visRows > 0) {
+ rowsPerColumn = visRows;
+ columnCount = Math.max(1, dataModelSize / rowsPerColumn);
+ if (dataModelSize > 0 && dataModelSize > rowsPerColumn &&
+ dataModelSize % rowsPerColumn != 0) {
+ columnCount++;
+ }
+ if (layoutOrientation == JList.HORIZONTAL_WRAP) {
+ // Because HORIZONTAL_WRAP flows differently, the
+ // rowsPerColumn needs to be adjusted.
+ rowsPerColumn = (dataModelSize / columnCount);
+ if (dataModelSize % columnCount > 0) {
+ rowsPerColumn++;
+ }
+ }
+ } else if (layoutOrientation == JList.VERTICAL_WRAP && height != 0) {
+ rowsPerColumn = Math.max(1, (listHeight - insets.top -
+ insets.bottom) / height);
+ columnCount = Math.max(1, dataModelSize / rowsPerColumn);
+ if (dataModelSize > 0 && dataModelSize > rowsPerColumn &&
+ dataModelSize % rowsPerColumn != 0) {
+ columnCount++;
+ }
+ } else if (layoutOrientation == JList.HORIZONTAL_WRAP && cellWidth > 0 &&
+ listWidth > 0) {
+ columnCount = Math.max(1, (listWidth - insets.left -
+ insets.right) / cellWidth);
+ rowsPerColumn = dataModelSize / columnCount;
+ if (dataModelSize % columnCount > 0) {
+ rowsPerColumn++;
+ }
+ }
+ preferredHeight = rowsPerColumn * cellHeight + insets.top +
+ insets.bottom;
+ }
+
+ protected void paintImpl(final Graphics g, final JComponent c) {
+ switch (layoutOrientation) {
+ case JList.VERTICAL_WRAP:
+ if (list.getHeight() != listHeight) {
+ updateLayoutStateNeeded |= heightChanged;
+ redrawList();
+ }
+ break;
+ case JList.HORIZONTAL_WRAP:
+ if (list.getWidth() != listWidth) {
+ updateLayoutStateNeeded |= widthChanged;
+ redrawList();
+ }
+ break;
+ default:
+ break;
+ }
+ maybeUpdateLayoutState();
+
+ ListCellRenderer renderer = list.getCellRenderer();
+ ListModel dataModel = list.getModel();
+ ListSelectionModel selModel = list.getSelectionModel();
+ int size;
+
+ if ((renderer == null) || (size = dataModel.getSize()) == 0) {
+ return;
+ }
+
+ // Determine how many columns we need to paint
+ Rectangle paintBounds = g.getClipBounds();
+
+ int startColumn, endColumn;
+ if (c.getComponentOrientation().isLeftToRight()) {
+ startColumn = convertLocationToColumn(paintBounds.x,
+ paintBounds.y);
+ endColumn = convertLocationToColumn(paintBounds.x +
+ paintBounds.width,
+ paintBounds.y);
+ } else {
+ startColumn = convertLocationToColumn(paintBounds.x +
+ paintBounds.width,
+ paintBounds.y);
+ endColumn = convertLocationToColumn(paintBounds.x,
+ paintBounds.y);
+ }
+ int maxY = paintBounds.y + paintBounds.height;
+ int leadIndex = adjustIndex(list.getLeadSelectionIndex(), list);
+ int rowIncrement = (layoutOrientation == JList.HORIZONTAL_WRAP) ?
+ columnCount : 1;
+
+
+ for (int colCounter = startColumn; colCounter <= endColumn;
+ colCounter++) {
+ // And then how many rows in this columnn
+ int row = convertLocationToRowInColumn(paintBounds.y, colCounter);
+ int rowCount = getRowCount(colCounter);
+ int index = getModelIndex(colCounter, row);
+ Rectangle rowBounds = getCellBounds(list, index, index);
+
+ if (rowBounds == null) {
+ // Not valid, bail!
+ return;
+ }
+ while (row < rowCount && rowBounds.y < maxY &&
+ index < size) {
+ rowBounds.height = getHeight(colCounter, row);
+ g.setClip(rowBounds.x, rowBounds.y, rowBounds.width,
+ rowBounds.height);
+ g.clipRect(paintBounds.x, paintBounds.y, paintBounds.width,
+ paintBounds.height);
+ paintCell(g, index, rowBounds, renderer, dataModel, selModel,
+ leadIndex);
+ rowBounds.y += rowBounds.height;
+ index += rowIncrement;
+ row++;
+ }
+ }
+ // Empty out the renderer pane, allowing renderers to be gc'ed.
+ rendererPane.removeAll();
+ }
+
+ protected void paintDropLine(final Graphics g) {
+ JList.DropLocation loc = list.getDropLocation();
+ if (loc == null || !loc.isInsert()) {
+ return;
+ }
+
+ Color c = DefaultLookup.getColor(list, this, "List.dropLineColor", null);
+ if (c != null) {
+ g.setColor(c);
+ Rectangle rect = getDropLineRect(loc);
+ g.fillRect(rect.x, rect.y, rect.width, rect.height);
+ }
+ }
+
+ protected Rectangle getDropLineRect(final JList.DropLocation loc) {
+ int size = list.getModel().getSize();
+
+ if (size == 0) {
+ Insets insets = list.getInsets();
+ if (layoutOrientation == JList.HORIZONTAL_WRAP) {
+ if (isLeftToRight) {
+ return new Rectangle(insets.left, insets.top, DROP_LINE_THICKNESS, 20);
+ } else {
+ return new Rectangle(list.getWidth() - DROP_LINE_THICKNESS - insets.right,
+ insets.top, DROP_LINE_THICKNESS, 20);
+ }
+ } else {
+ return new Rectangle(insets.left, insets.top,
+ list.getWidth() - insets.left - insets.right,
+ DROP_LINE_THICKNESS);
+ }
+ }
+
+ Rectangle rect = null;
+ int index = loc.getIndex();
+ boolean decr = false;
+
+ if (layoutOrientation == JList.HORIZONTAL_WRAP) {
+ if (index == size) {
+ decr = true;
+ } else if (index != 0 && convertModelToRow(index)
+ != convertModelToRow(index - 1)) {
+
+ Rectangle prev = getCellBounds(list, index - 1);
+ Rectangle me = getCellBounds(list, index);
+ Point p = loc.getDropPoint();
+
+ if (isLeftToRight) {
+ decr = Point2D.distance(prev.x + prev.width,
+ prev.y + (int) (prev.height / 2.0),
+ p.x, p.y)
+ < Point2D.distance(me.x,
+ me.y + (int) (me.height / 2.0),
+ p.x, p.y);
+ } else {
+ decr = Point2D.distance(prev.x,
+ prev.y + (int) (prev.height / 2.0),
+ p.x, p.y)
+ < Point2D.distance(me.x + me.width,
+ me.y + (int) (prev.height / 2.0),
+ p.x, p.y);
+ }
+ }
+
+ if (decr) {
+ index--;
+ rect = getCellBounds(list, index);
+ if (isLeftToRight) {
+ rect.x += rect.width;
+ } else {
+ rect.x -= DROP_LINE_THICKNESS;
+ }
+ } else {
+ rect = getCellBounds(list, index);
+ if (!isLeftToRight) {
+ rect.x += rect.width - DROP_LINE_THICKNESS;
+ }
+ }
+
+ if (rect.x >= list.getWidth()) {
+ rect.x = list.getWidth() - DROP_LINE_THICKNESS;
+ } else if (rect.x < 0) {
+ rect.x = 0;
+ }
+
+ rect.width = DROP_LINE_THICKNESS;
+ } else if (layoutOrientation == JList.VERTICAL_WRAP) {
+ if (index == size) {
+ index--;
+ rect = getCellBounds(list, index);
+ rect.y += rect.height;
+ } else if (index != 0 && convertModelToColumn(index)
+ != convertModelToColumn(index - 1)) {
+
+ Rectangle prev = getCellBounds(list, index - 1);
+ Rectangle me = getCellBounds(list, index);
+ Point p = loc.getDropPoint();
+ if (Point2D.distance(prev.x + (int) (prev.width / 2.0),
+ prev.y + prev.height,
+ p.x, p.y)
+ < Point2D.distance(me.x + (int) (me.width / 2.0),
+ me.y,
+ p.x, p.y)) {
+
+ index--;
+ rect = getCellBounds(list, index);
+ rect.y += rect.height;
+ } else {
+ rect = getCellBounds(list, index);
+ }
+ } else {
+ rect = getCellBounds(list, index);
+ }
+
+ if (rect.y >= list.getHeight()) {
+ rect.y = list.getHeight() - DROP_LINE_THICKNESS;
+ }
+
+ rect.height = DROP_LINE_THICKNESS;
+ } else {
+ if (index == size) {
+ index--;
+ rect = getCellBounds(list, index);
+ rect.y += rect.height;
+ } else {
+ rect = getCellBounds(list, index);
+ }
+
+ if (rect.y >= list.getHeight()) {
+ rect.y = list.getHeight() - DROP_LINE_THICKNESS;
+ }
+
+ rect.height = DROP_LINE_THICKNESS;
+ }
+
+ return rect;
+ }
+
+ /**
+ * Gets the bounds of the specified model index, returning the resulting
+ * bounds, or null if index
is not valid.
+ */
+ protected Rectangle getCellBounds(final JList> list, final int index) {
+ maybeUpdateLayoutState();
+
+ int row = convertModelToRow(index);
+ int column = convertModelToColumn(index);
+
+ if (row == -1 || column == -1) {
+ return null;
+ }
+
+ Insets insets = list.getInsets();
+ int x;
+ int w = cellWidth;
+ int y = insets.top;
+ int h;
+ switch (layoutOrientation) {
+ case JList.VERTICAL_WRAP:
+ case JList.HORIZONTAL_WRAP:
+ if (isLeftToRight) {
+ x = insets.left + column * cellWidth;
+ } else {
+ x = list.getWidth() - insets.right - (column + 1) * cellWidth;
+ }
+ y += cellHeight * row;
+ h = cellHeight;
+ break;
+ default:
+ x = insets.left;
+ if (cellHeights == null) {
+ y += (cellHeight * row);
+ } else if (row >= cellHeights.length) {
+ y = 0;
+ } else {
+ for (int i = 0; i < row; i++) {
+ y += cellHeights[i];
+ }
+ }
+ w = list.getWidth() - (insets.left + insets.right);
+ h = getRowHeight(index);
+ break;
+ }
+ return new Rectangle(x, y, w, h);
+ }
+
+ /**
+ * Returns the height of the cell at the passed in location.
+ */
+ protected int getHeight(final int column, final int row) {
+ if (column < 0 || column > columnCount || row < 0) {
+ return -1;
+ }
+ if (layoutOrientation != JList.VERTICAL) {
+ return cellHeight;
+ }
+ if (row >= list.getModel().getSize()) {
+ return -1;
+ }
+ return (cellHeights == null) ? cellHeight :
+ ((row < cellHeights.length) ? cellHeights[row] : -1);
+ }
+
+ /**
+ * Returns the closest row that starts at the specified y-location
+ * in the passed in column.
+ */
+ protected int convertLocationToRowInColumn(final int y, final int column) {
+ int x = 0;
+
+ if (layoutOrientation != JList.VERTICAL) {
+ if (isLeftToRight) {
+ x = column * cellWidth;
+ } else {
+ x = list.getWidth() - (column + 1) * cellWidth - list.getInsets().right;
+ }
+ }
+ return convertLocationToRow(x, y, true);
+ }
+
+ /**
+ * Returns the number of rows in the given column.
+ */
+ protected int getRowCount(final int column) {
+ if (column < 0 || column >= columnCount) {
+ return -1;
+ }
+ if (layoutOrientation == JList.VERTICAL ||
+ (column == 0 && columnCount == 1)) {
+ return list.getModel().getSize();
+ }
+ if (column >= columnCount) {
+ return -1;
+ }
+ if (layoutOrientation == JList.VERTICAL_WRAP) {
+ if (column < (columnCount - 1)) {
+ return rowsPerColumn;
+ }
+ return list.getModel().getSize() - (columnCount - 1) *
+ rowsPerColumn;
+ }
+ // JList.HORIZONTAL_WRAP
+ int diff = columnCount - (columnCount * rowsPerColumn -
+ list.getModel().getSize());
+
+ if (column >= diff) {
+ return Math.max(0, rowsPerColumn - 1);
+ }
+ return rowsPerColumn;
+ }
+
+ /**
+ * Returns the row that the model index index
will be
+ * displayed in..
+ */
+ protected int convertModelToRow(final int index) {
+ int size = list.getModel().getSize();
+
+ if ((index < 0) || (index >= size)) {
+ return -1;
+ }
+
+ if (layoutOrientation != JList.VERTICAL && columnCount > 1 &&
+ rowsPerColumn > 0) {
+ if (layoutOrientation == JList.VERTICAL_WRAP) {
+ return index % rowsPerColumn;
+ }
+ return index / columnCount;
+ }
+ return index;
+ }
+
+ /**
+ * Returns the column that the model index index
will be
+ * displayed in.
+ */
+ protected int convertModelToColumn(final int index) {
+ int size = list.getModel().getSize();
+
+ if ((index < 0) || (index >= size)) {
+ return -1;
+ }
+
+ if (layoutOrientation != JList.VERTICAL && rowsPerColumn > 0 &&
+ columnCount > 1) {
+ if (layoutOrientation == JList.VERTICAL_WRAP) {
+ return index / rowsPerColumn;
+ }
+ return index % columnCount;
+ }
+ return 0;
+ }
+
+ protected void redrawList() {
+ list.revalidate();
+ list.repaint();
+ }
+
+ @SuppressWarnings("serial") // Superclass is a JDK-implementation class
+ static class ListTransferHandler extends TransferHandler implements UIResource {
+
+ public int getSourceActions(final JComponent c) {
+ return COPY;
+ }
+
+ /**
+ * Create a Transferable to use as the source for a data transfer.
+ *
+ * @param c The component holding the data to be transfered. This
+ * argument is provided to enable sharing of TransferHandlers by
+ * multiple components.
+ * @return The representation of the data to be transfered.
+ */
+ @SuppressWarnings("deprecation")
+ protected Transferable createTransferable(final JComponent c) {
+ if (c instanceof JList) {
+ JList> list = (JList) c;
+ Object[] values = list.getSelectedValues();
+
+ if (values == null || values.length == 0) {
+ return null;
+ }
+
+ StringBuilder plainStr = new StringBuilder();
+ StringBuilder htmlStr = new StringBuilder();
+
+ htmlStr.append("\n\n\n");
+
+ for (int i = 0; i < values.length; i++) {
+ Object obj = values[i];
+ String val = ((obj == null) ? "" : obj.toString());
+ plainStr.append(val).append('\n');
+ htmlStr.append(" ").append(val).append('\n');
+ }
+
+ // remove the last newline
+ plainStr.deleteCharAt(plainStr.length() - 1);
+ htmlStr.append(" \n\n");
+
+ return new BasicTransferable(plainStr.toString(), htmlStr.toString());
+ }
+
+ return null;
+ }
+
+ }
+
+ /**
+ * Mouse input, and focus handling for JList. An instance of this
+ * class is added to the appropriate java.awt.Component lists
+ * at installUI() time. Note keyboard input is handled with JComponent
+ * KeyboardActions, see installKeyboardActions().
+ *
+ * Warning:
+ * Serialized objects of this class will not be compatible with
+ * future Swing releases. The current serialization support is
+ * appropriate for short term storage or RMI between applications running
+ * the same version of Swing. As of 1.4, support for long term storage
+ * of all JavaBeans™
+ * has been added to the java.beans
package.
+ * Please see {@link java.beans.XMLEncoder}.
+ *
+ * @see #createMouseInputListener
+ * @see #installKeyboardActions
+ * @see #installUI
+ */
+ @SuppressWarnings("serial") // Same-version serialization only
+ public class MouseInputHandler implements MouseInputListener {
+ public void mouseClicked(final MouseEvent e) {
+ getHandler().mouseClicked(e);
+ }
+
+ public void mousePressed(final MouseEvent e) {
+ getHandler().mousePressed(e);
+ }
+
+ public void mouseReleased(final MouseEvent e) {
+ getHandler().mouseReleased(e);
+ }
+
+ public void mouseEntered(final MouseEvent e) {
+ getHandler().mouseEntered(e);
+ }
+
+ public void mouseExited(final MouseEvent e) {
+ getHandler().mouseExited(e);
+ }
+
+ public void mouseDragged(final MouseEvent e) {
+ getHandler().mouseDragged(e);
+ }
+
+ public void mouseMoved(final MouseEvent e) {
+ getHandler().mouseMoved(e);
+ }
+ }
+
+ /**
+ * This class should be treated as a "protected" inner class.
+ * Instantiate it only within subclasses of {@code BasicListUI}.
+ */
+ public class FocusHandler implements FocusListener {
+ /**
+ * Repaints focused cells.
+ */
+ protected void repaintCellFocus() {
+ getHandler().repaintCellFocus();
+ }
+
+ /* The focusGained() focusLost() methods run when the JList
+ * focus changes.
+ */
+
+ public void focusGained(final FocusEvent e) {
+ getHandler().focusGained(e);
+ }
+
+ public void focusLost(final FocusEvent e) {
+ getHandler().focusLost(e);
+ }
+ }
+
+ /**
+ * The ListSelectionListener that's added to the JLists selection
+ * model at installUI time, and whenever the JList.selectionModel property
+ * changes. When the selection changes we repaint the affected rows.
+ *
+ * Warning:
+ * Serialized objects of this class will not be compatible with
+ * future Swing releases. The current serialization support is
+ * appropriate for short term storage or RMI between applications running
+ * the same version of Swing. As of 1.4, support for long term storage
+ * of all JavaBeans™
+ * has been added to the java.beans
package.
+ * Please see {@link java.beans.XMLEncoder}.
+ *
+ * @see #createListSelectionListener
+ * @see #installUI
+ */
+ @SuppressWarnings("serial") // Same-version serialization only
+ public class ListSelectionHandler implements ListSelectionListener {
+ public void valueChanged(final ListSelectionEvent e) {
+ getHandler().valueChanged(e);
+ }
+ }
+
+ /**
+ * The {@code ListDataListener} that's added to the {@code JLists} model at
+ * {@code installUI time}, and whenever the JList.model property changes.
+ *
+ * Warning:
+ * Serialized objects of this class will not be compatible with
+ * future Swing releases. The current serialization support is
+ * appropriate for short term storage or RMI between applications running
+ * the same version of Swing. As of 1.4, support for long term storage
+ * of all JavaBeans™
+ * has been added to the java.beans
package.
+ * Please see {@link java.beans.XMLEncoder}.
+ *
+ * @see JList#getModel
+ * @see #maybeUpdateLayoutState
+ * @see #createListDataListener
+ * @see #installUI
+ */
+ @SuppressWarnings("serial") // Same-version serialization only
+ public class ListDataHandler implements ListDataListener {
+ public void intervalAdded(final ListDataEvent e) {
+ getHandler().intervalAdded(e);
+ }
+
+
+ public void intervalRemoved(final ListDataEvent e) {
+ getHandler().intervalRemoved(e);
+ }
+
+
+ public void contentsChanged(final ListDataEvent e) {
+ getHandler().contentsChanged(e);
+ }
+ }
+
+ /**
+ * The PropertyChangeListener that's added to the JList at
+ * installUI time. When the value of a JList property that
+ * affects layout changes, we set a bit in updateLayoutStateNeeded.
+ * If the JLists model changes we additionally remove our listeners
+ * from the old model. Likewise for the JList selectionModel.
+ *
+ * Warning:
+ * Serialized objects of this class will not be compatible with
+ * future Swing releases. The current serialization support is
+ * appropriate for short term storage or RMI between applications running
+ * the same version of Swing. As of 1.4, support for long term storage
+ * of all JavaBeans™
+ * has been added to the java.beans
package.
+ * Please see {@link java.beans.XMLEncoder}.
+ *
+ * @see #maybeUpdateLayoutState
+ * @see #createPropertyChangeListener
+ * @see #installUI
+ */
+ @SuppressWarnings("serial") // Same-version serialization only
+ public class PropertyChangeHandler implements PropertyChangeListener {
+ public void propertyChange(final PropertyChangeEvent e) {
+ getHandler().propertyChange(e);
+ }
+ }
+
+ protected class Handler implements FocusListener, KeyListener,
+ ListDataListener, ListSelectionListener,
+ MouseInputListener, PropertyChangeListener,
+ DragRecognitionSupport.BeforeDrag {
+ //
+ // KeyListener
+ //
+ protected String prefix = "";
+ protected String typedString = "";
+ protected long lastTime = 0L;
+ // Whether or not the mouse press (which is being considered as part
+ // of a drag sequence) also caused the selection change to be fully
+ // processed.
+ protected boolean dragPressDidSelection;
+
+ /**
+ * Invoked when a key has been typed.
+ *
+ * Moves the keyboard focus to the first element whose prefix matches the
+ * sequence of alphanumeric keys pressed by the user with delay less
+ * than value of timeFactor
property (or 1000 milliseconds
+ * if it is not defined). Subsequent same key presses move the keyboard
+ * focus to the next object that starts with the same letter until another
+ * key is pressed, then it is treated as the prefix with appropriate number
+ * of the same letters followed by first typed another letter.
+ */
+ public void keyTyped(final KeyEvent e) {
+ JList> src = (JList) e.getSource();
+ ListModel> model = src.getModel();
+
+ if (model.getSize() == 0 || e.isAltDown() ||
+ DarkUIUtil.isMenuShortcutKeyDown(e) ||
+ isNavigationKey(e)) {
+ // Nothing to select
+ return;
+ }
+ boolean startingFromSelection = true;
+
+ char c = e.getKeyChar();
+
+ long time = e.getWhen();
+ int startIndex = adjustIndex(src.getLeadSelectionIndex(), list);
+ if (time - lastTime < timeFactor) {
+ typedString += c;
+ if ((prefix.length() == 1) && (c == prefix.charAt(0))) {
+ // Subsequent same key presses move the keyboard focus to the next
+ // object that starts with the same letter.
+ startIndex++;
+ } else {
+ prefix = typedString;
+ }
+ } else {
+ startIndex++;
+ typedString = "" + c;
+ prefix = typedString;
+ }
+ lastTime = time;
+
+ if (startIndex < 0 || startIndex >= model.getSize()) {
+ startingFromSelection = false;
+ startIndex = 0;
+ }
+ int index = src.getNextMatch(prefix, startIndex,
+ Position.Bias.Forward);
+ if (index >= 0) {
+ src.setSelectedIndex(index);
+ src.ensureIndexIsVisible(index);
+ } else if (startingFromSelection) { // wrap
+ index = src.getNextMatch(prefix, 0,
+ Position.Bias.Forward);
+ if (index >= 0) {
+ src.setSelectedIndex(index);
+ src.ensureIndexIsVisible(index);
+ }
+ }
+ }
+
+ /**
+ * Invoked when a key has been pressed.
+ *
+ * Checks to see if the key event is a navigation key to prevent
+ * dispatching these keys for the first letter navigation.
+ */
+ public void keyPressed(final KeyEvent e) {
+ if (isNavigationKey(e)) {
+ prefix = "";
+ typedString = "";
+ lastTime = 0L;
+ }
+ }
+
+ /**
+ * Invoked when a key has been released.
+ * See the class description for {@link KeyEvent} for a definition of
+ * a key released event.
+ */
+ public void keyReleased(final KeyEvent e) {
+ }
+
+ /**
+ * Returns whether or not the supplied key event maps to a key that is used for
+ * navigation. This is used for optimizing key input by only passing non-
+ * navigation keys to the first letter navigation mechanism.
+ */
+ protected boolean isNavigationKey(final KeyEvent event) {
+ InputMap inputMap = list.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
+ KeyStroke key = KeyStroke.getKeyStrokeForEvent(event);
+
+ return inputMap != null && inputMap.get(key) != null;
+ }
+
+ //
+ // PropertyChangeListener
+ //
+ public void propertyChange(final PropertyChangeEvent e) {
+ String propertyName = e.getPropertyName();
+
+ /* If the JList.model property changes, remove our listener,
+ * listDataListener from the old model and add it to the new one.
+ */
+ if (Objects.equals(propertyName, "model")) {
+ ListModel> oldModel = (ListModel) e.getOldValue();
+ ListModel> newModel = (ListModel) e.getNewValue();
+ if (oldModel != null) {
+ oldModel.removeListDataListener(listDataListener);
+ }
+ if (newModel != null) {
+ newModel.addListDataListener(listDataListener);
+ }
+ updateLayoutStateNeeded |= modelChanged;
+ redrawList();
+ }
+
+ /* If the JList.selectionModel property changes, remove our listener,
+ * listSelectionListener from the old selectionModel and add it to the new one.
+ */
+ else if (Objects.equals(propertyName, "selectionModel")) {
+ ListSelectionModel oldModel = (ListSelectionModel) e.getOldValue();
+ ListSelectionModel newModel = (ListSelectionModel) e.getNewValue();
+ if (oldModel != null) {
+ oldModel.removeListSelectionListener(listSelectionListener);
+ }
+ if (newModel != null) {
+ newModel.addListSelectionListener(listSelectionListener);
+ }
+ updateLayoutStateNeeded |= modelChanged;
+ redrawList();
+ } else if (Objects.equals(propertyName, "cellRenderer")) {
+ updateLayoutStateNeeded |= cellRendererChanged;
+ redrawList();
+ } else if (Objects.equals(propertyName, "font")
+ || SwingUtilities2.isScaleChanged(e)) {
+ updateLayoutStateNeeded |= fontChanged;
+ redrawList();
+ } else if (Objects.equals(propertyName, "prototypeCellValue")) {
+ updateLayoutStateNeeded |= prototypeCellValueChanged;
+ redrawList();
+ } else if (Objects.equals(propertyName, "fixedCellHeight")) {
+ updateLayoutStateNeeded |= fixedCellHeightChanged;
+ redrawList();
+ } else if (Objects.equals(propertyName, "fixedCellWidth")) {
+ updateLayoutStateNeeded |= fixedCellWidthChanged;
+ redrawList();
+ } else if (Objects.equals(propertyName, "selectionForeground")) {
+ list.repaint();
+ } else if (Objects.equals(propertyName, "selectionBackground")) {
+ list.repaint();
+ } else if ("layoutOrientation".equals(propertyName)) {
+ updateLayoutStateNeeded |= layoutOrientationChanged;
+ layoutOrientation = list.getLayoutOrientation();
+ redrawList();
+ } else if ("visibleRowCount".equals(propertyName)) {
+ if (layoutOrientation != JList.VERTICAL) {
+ updateLayoutStateNeeded |= layoutOrientationChanged;
+ redrawList();
+ }
+ } else if ("componentOrientation".equals(propertyName)) {
+ isLeftToRight = list.getComponentOrientation().isLeftToRight();
+ updateLayoutStateNeeded |= componentOrientationChanged;
+ redrawList();
+
+ InputMap inputMap = getInputMap(JComponent.WHEN_FOCUSED);
+ SwingUtilities.replaceUIInputMap(list, JComponent.WHEN_FOCUSED,
+ inputMap);
+ } else if ("List.isFileList".equals(propertyName)) {
+ updateIsFileList();
+ redrawList();
+ } else if ("dropLocation".equals(propertyName)) {
+ JList.DropLocation oldValue = (JList.DropLocation) e.getOldValue();
+ repaintDropLocation(oldValue);
+ repaintDropLocation(list.getDropLocation());
+ }
+ }
+
+ protected void repaintDropLocation(final JList.DropLocation loc) {
+ if (loc == null) {
+ return;
+ }
+
+ Rectangle r;
+
+ if (loc.isInsert()) {
+ r = getDropLineRect(loc);
+ } else {
+ r = getCellBounds(list, loc.getIndex());
+ }
+
+ if (r != null) {
+ list.repaint(r);
+ }
+ }
+
+ //
+ // ListDataListener
+ //
+ public void intervalAdded(final ListDataEvent e) {
+ updateLayoutStateNeeded = modelChanged;
+
+ int minIndex = Math.min(e.getIndex0(), e.getIndex1());
+ int maxIndex = Math.max(e.getIndex0(), e.getIndex1());
+
+ /* Sync the SelectionModel with the DataModel.
+ */
+
+ ListSelectionModel sm = list.getSelectionModel();
+ if (sm != null) {
+ sm.insertIndexInterval(minIndex, maxIndex - minIndex + 1, true);
+ }
+
+ /* Repaint the entire list, from the origin of
+ * the first added cell, to the bottom of the
+ * component.
+ */
+ redrawList();
+ }
+
+ public void intervalRemoved(final ListDataEvent e) {
+ updateLayoutStateNeeded = modelChanged;
+
+ /* Sync the SelectionModel with the DataModel.
+ */
+
+ ListSelectionModel sm = list.getSelectionModel();
+ if (sm != null) {
+ sm.removeIndexInterval(e.getIndex0(), e.getIndex1());
+ }
+
+ /* Repaint the entire list, from the origin of
+ * the first removed cell, to the bottom of the
+ * component.
+ */
+
+ redrawList();
+ }
+
+ public void contentsChanged(final ListDataEvent e) {
+ updateLayoutStateNeeded = modelChanged;
+ redrawList();
+ }
+
+ //
+ // ListSelectionListener
+ //
+ public void valueChanged(final ListSelectionEvent e) {
+ maybeUpdateLayoutState();
+
+ int size = list.getModel().getSize();
+ int firstIndex = Math.min(size - 1, Math.max(e.getFirstIndex(), 0));
+ int lastIndex = Math.min(size - 1, Math.max(e.getLastIndex(), 0));
+
+ Rectangle bounds = getCellBounds(list, firstIndex, lastIndex);
+
+ if (bounds != null) {
+ list.repaint(bounds.x, bounds.y, bounds.width, bounds.height);
+ }
+ }
+
+ //
+ // MouseListener
+ //
+ public void mouseClicked(final MouseEvent e) {
+ }
+
+ public void mousePressed(final MouseEvent e) {
+ if (SwingUtilities2.shouldIgnore(e, list)) {
+ return;
+ }
+
+ boolean dragEnabled = list.getDragEnabled();
+ boolean grabFocus = true;
+
+ // different behavior if drag is enabled
+ if (dragEnabled) {
+ int row = SwingUtilities2.loc2IndexFileList(list, e.getPoint());
+ // if we have a valid row and this is a drag initiating event
+ if (row != -1 && DragRecognitionSupport.mousePressed(e)) {
+ dragPressDidSelection = false;
+
+ if (DarkUIUtil.isMenuShortcutKeyDown(e)) {
+ // do nothing for control - will be handled on release
+ // or when drag starts
+ return;
+ } else if (!e.isShiftDown() && list.isSelectedIndex(row)) {
+ // clicking on something that's already selected
+ // and need to make it the lead now
+ list.addSelectionInterval(row, row);
+ return;
+ }
+
+ // could be a drag initiating event - don't grab focus
+ grabFocus = false;
+
+ dragPressDidSelection = true;
+ }
+ } else {
+ // When drag is enabled mouse drags won't change the selection
+ // in the list, so we only set the isAdjusting flag when it's
+ // not enabled
+ list.setValueIsAdjusting(true);
+ }
+
+ if (grabFocus) {
+ SwingUtilities2.adjustFocus(list);
+ }
+
+ adjustSelection(e);
+ }
+
+ protected void adjustSelection(final MouseEvent e) {
+ int row = SwingUtilities2.loc2IndexFileList(list, e.getPoint());
+ if (row < 0) {
+ // If shift is down in multi-select, we should do nothing.
+ // For single select or non-shift-click, clear the selection
+ if (isFileList && e.getID() == MouseEvent.MOUSE_PRESSED &&
+ (!e.isShiftDown() ||
+ list.getSelectionMode() == ListSelectionModel.SINGLE_SELECTION)) {
+ list.clearSelection();
+ }
+ } else {
+ int anchorIndex = adjustIndex(list.getAnchorSelectionIndex(), list);
+ boolean anchorSelected;
+ if (anchorIndex == -1) {
+ anchorIndex = 0;
+ anchorSelected = false;
+ } else {
+ anchorSelected = list.isSelectedIndex(anchorIndex);
+ }
+
+ if (DarkUIUtil.isMenuShortcutKeyDown(e)) {
+ if (e.isShiftDown()) {
+ if (anchorSelected) {
+ list.addSelectionInterval(anchorIndex, row);
+ } else {
+ list.removeSelectionInterval(anchorIndex, row);
+ if (isFileList) {
+ list.addSelectionInterval(row, row);
+ list.getSelectionModel().setAnchorSelectionIndex(anchorIndex);
+ }
+ }
+ } else if (list.isSelectedIndex(row)) {
+ list.removeSelectionInterval(row, row);
+ } else {
+ list.addSelectionInterval(row, row);
+ }
+ } else if (e.isShiftDown()) {
+ list.setSelectionInterval(anchorIndex, row);
+ } else {
+ list.setSelectionInterval(row, row);
+ }
+ }
+ }
+
+ public void mouseReleased(final MouseEvent e) {
+ if (SwingUtilities2.shouldIgnore(e, list)) {
+ return;
+ }
+
+ if (list.getDragEnabled()) {
+ MouseEvent me = DragRecognitionSupport.mouseReleased(e);
+ if (me != null) {
+ SwingUtilities2.adjustFocus(list);
+ if (!dragPressDidSelection) {
+ adjustSelection(me);
+ }
+ }
+ } else {
+ list.setValueIsAdjusting(false);
+ }
+ }
+
+ public void mouseEntered(final MouseEvent e) {
+ }
+
+ public void mouseExited(final MouseEvent e) {
+ }
+
+ public void dragStarting(final MouseEvent me) {
+ if (DarkUIUtil.isMenuShortcutKeyDown(me)) {
+ int row = SwingUtilities2.loc2IndexFileList(list, me.getPoint());
+ list.addSelectionInterval(row, row);
+ }
+ }
+
+ public void mouseDragged(final MouseEvent e) {
+ if (SwingUtilities2.shouldIgnore(e, list)) {
+ return;
+ }
+
+ if (list.getDragEnabled()) {
+ DragRecognitionSupport.mouseDragged(e, this);
+ return;
+ }
+
+ if (e.isShiftDown() || DarkUIUtil.isMenuShortcutKeyDown(e)) {
+ return;
+ }
+
+ int row = locationToIndex(list, e.getPoint());
+ if (row != -1) {
+ // 4835633. Dragging onto a File should not select it.
+ if (isFileList) {
+ return;
+ }
+ Rectangle cellBounds = getCellBounds(list, row, row);
+ if (cellBounds != null) {
+ list.scrollRectToVisible(cellBounds);
+ list.setSelectionInterval(row, row);
+ }
+ }
+ }
+
+ public void mouseMoved(final MouseEvent e) {
+ }
+
+ public void focusGained(final FocusEvent e) {
+ repaintCellFocus();
+ }
+
+ /* The focusGained() focusLost() methods run when the JList
+ * focus changes.
+ */
+
+ //
+ // FocusListener
+ //
+ protected void repaintCellFocus() {
+ int leadIndex = adjustIndex(list.getLeadSelectionIndex(), list);
+ if (leadIndex != -1) {
+ Rectangle r = getCellBounds(list, leadIndex, leadIndex);
+ if (r != null) {
+ list.repaint(r.x, r.y, r.width, r.height);
+ }
+ }
+ }
+
+ public void focusLost(final FocusEvent e) {
+ repaintCellFocus();
+ }
+ }
+}
diff --git a/src/main/java/com/weis/darklaf/ui/menu/DarkMenuItemUIBase.java b/src/main/java/com/weis/darklaf/ui/menu/DarkMenuItemUIBase.java
index 485b53ca..f26d51db 100644
--- a/src/main/java/com/weis/darklaf/ui/menu/DarkMenuItemUIBase.java
+++ b/src/main/java/com/weis/darklaf/ui/menu/DarkMenuItemUIBase.java
@@ -27,27 +27,15 @@ import com.weis.darklaf.util.DarkUIUtil;
import com.weis.darklaf.util.LazyActionMap;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
-import sun.swing.MenuItemCheckIconFactory;
import sun.swing.MenuItemLayoutHelper;
import sun.swing.SwingUtilities2;
import sun.swing.UIAction;
import javax.swing.*;
-import javax.swing.event.MenuDragMouseEvent;
-import javax.swing.event.MenuDragMouseListener;
-import javax.swing.event.MouseInputListener;
-import javax.swing.plaf.ComponentInputMapUIResource;
import javax.swing.plaf.ComponentUI;
-import javax.swing.plaf.UIResource;
-import javax.swing.plaf.basic.BasicHTML;
import javax.swing.plaf.basic.BasicMenuItemUI;
import java.awt.*;
import java.awt.event.ActionEvent;
-import java.awt.event.InputEvent;
-import java.awt.event.MouseEvent;
-import java.beans.PropertyChangeEvent;
-import java.beans.PropertyChangeListener;
-import java.util.Objects;
/**
* @author Konstantin Bulenkov
@@ -55,8 +43,6 @@ import java.util.Objects;
*/
public class DarkMenuItemUIBase extends BasicMenuItemUI {
- protected Handler handler;
-
@NotNull
@Contract("_ -> new")
public static ComponentUI createUI(final JComponent c) {
@@ -256,103 +242,8 @@ public class DarkMenuItemUIBase extends BasicMenuItemUI {
}
}
- /*
- * Code from BasicMenuItemUI.
- */
-
- protected DarkMenuItemUIBase.Handler getHandler() {
- if (handler == null) {
- handler = new DarkMenuItemUIBase.Handler();
- }
- return handler;
- }
-
- protected void updateAcceleratorBinding() {
- KeyStroke accelerator = menuItem.getAccelerator();
- InputMap windowInputMap = SwingUtilities.getUIInputMap(menuItem, JComponent.WHEN_IN_FOCUSED_WINDOW);
-
- if (windowInputMap != null) {
- windowInputMap.clear();
- }
- if (accelerator != null) {
- if (windowInputMap == null) {
- windowInputMap = createInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
- SwingUtilities.replaceUIInputMap(menuItem, JComponent.WHEN_IN_FOCUSED_WINDOW, windowInputMap);
- }
- windowInputMap.put(accelerator, "doClick");
-
- int modifiers = accelerator.getModifiers();
- if (((modifiers & InputEvent.ALT_DOWN_MASK) != 0) &&
- ((modifiers & InputEvent.ALT_GRAPH_DOWN_MASK) != 0)) {
- //When both ALT and ALT_GRAPH are set, add the ALT only
- // modifier keystroke which is used for left ALT key.
- // Unsetting the ALT_GRAPH will do that as ALT is already set
- modifiers &= ~InputEvent.ALT_GRAPH_DOWN_MASK;
- modifiers &= ~InputEvent.ALT_GRAPH_MASK;
- KeyStroke keyStroke = KeyStroke.getKeyStroke(accelerator.getKeyCode(),
- modifiers, accelerator.isOnKeyRelease());
- windowInputMap.put(keyStroke, "doClick");
- } else if (((modifiers & InputEvent.ALT_DOWN_MASK) != 0) && (
- (modifiers & InputEvent.ALT_GRAPH_DOWN_MASK) == 0)) {
- //When only ALT modifier is set, add the ALT + ALT_GRAPH
- // modifier keystroke which is used for right ALT key
- modifiers |= InputEvent.ALT_GRAPH_DOWN_MASK;
- KeyStroke keyStroke = KeyStroke.getKeyStroke(accelerator.getKeyCode(),
- modifiers, accelerator.isOnKeyRelease());
- windowInputMap.put(keyStroke, "doClick");
- } else if ((modifiers & InputEvent.ALT_GRAPH_DOWN_MASK) != 0) {
- //When only ALT_GRAPH is set, remove the ALT_GRAPH only
- // modifier and add the ALT and ALT+ALT_GRAPH modifiers
- // keystroke which are used for left ALT key and right ALT
- // respectively
- modifiers &= ~InputEvent.ALT_GRAPH_DOWN_MASK;
- modifiers &= ~InputEvent.ALT_GRAPH_MASK;
-
- modifiers |= InputEvent.ALT_DOWN_MASK;
- KeyStroke keyStroke = KeyStroke.getKeyStroke(accelerator.getKeyCode(),
- modifiers, accelerator.isOnKeyRelease());
- windowInputMap.put(keyStroke, "doClick");
-
- //Add ALT+ALT_GRAPH modifier which is used for right ALT key
- modifiers |= InputEvent.ALT_GRAPH_DOWN_MASK;
- keyStroke = KeyStroke.getKeyStroke(accelerator.getKeyCode(),
- modifiers, accelerator.isOnKeyRelease());
- windowInputMap.put(keyStroke, "doClick");
- }
- }
- }
-
- protected InputMap createInputMap(final int condition) {
- if (condition == JComponent.WHEN_IN_FOCUSED_WINDOW) {
- return new ComponentInputMapUIResource(menuItem);
- }
- return null;
- }
-
- protected void updateCheckIcon() {
- String prefix = getPropertyPrefix();
-
- if (checkIcon == null || checkIcon instanceof UIResource) {
- checkIcon = UIManager.getIcon(prefix + ".checkIcon");
- //In case of column layout, .checkIconFactory is defined for this UI,
- //the icon is compatible with it and useCheckAndArrow() is true,
- //then the icon is handled by the checkIcon.
- boolean isColumnLayout = MenuItemLayoutHelper.isColumnLayout(
- menuItem.getComponentOrientation().isLeftToRight(), menuItem);
- if (isColumnLayout) {
- MenuItemCheckIconFactory iconFactory =
- (MenuItemCheckIconFactory) UIManager.get(prefix + ".checkIconFactory");
- if (iconFactory != null
- && MenuItemLayoutHelper.useCheckAndArrow(menuItem)
- && iconFactory.isCompatible(checkIcon, prefix)) {
- checkIcon = iconFactory.getIcon(menuItem);
- }
- }
- }
- }
-
- private static class Actions extends UIAction {
- private static final String CLICK = "doClick";
+ protected static class Actions extends UIAction {
+ protected static final String CLICK = "doClick";
Actions(final String key) {
super(key);
@@ -364,129 +255,4 @@ public class DarkMenuItemUIBase extends BasicMenuItemUI {
mi.doClick();
}
}
-
- protected class Handler implements MenuDragMouseListener, MouseInputListener, PropertyChangeListener {
- //
- // MouseInputListener
- //
- public void mouseClicked(final MouseEvent e) {
- }
-
- public void mousePressed(final MouseEvent e) {
- }
-
- public void mouseReleased(final MouseEvent e) {
- if (!menuItem.isEnabled()) {
- return;
- }
- MenuSelectionManager manager =
- MenuSelectionManager.defaultManager();
- Point p = e.getPoint();
- if (p.x >= 0 && p.x < menuItem.getWidth() &&
- p.y >= 0 && p.y < menuItem.getHeight()) {
- doClick(manager);
- } else {
- manager.processMouseEvent(e);
- }
- }
-
- @SuppressWarnings("deprecation")
- public void mouseEntered(@NotNull final MouseEvent e) {
- MenuSelectionManager manager = MenuSelectionManager.defaultManager();
- int modifiers = e.getModifiers();
- // 4188027: drag enter/exit added in JDK 1.1.7A, JDK1.2
- if ((modifiers & (InputEvent.BUTTON1_MASK |
- InputEvent.BUTTON2_MASK | InputEvent.BUTTON3_MASK)) != 0) {
- MenuSelectionManager.defaultManager().processMouseEvent(e);
- } else {
- manager.setSelectedPath(getPath());
- }
- }
-
- @SuppressWarnings("deprecation")
- public void mouseExited(@NotNull final MouseEvent e) {
- MenuSelectionManager manager = MenuSelectionManager.defaultManager();
-
- int modifiers = e.getModifiers();
- // 4188027: drag enter/exit added in JDK 1.1.7A, JDK1.2
- if ((modifiers & (InputEvent.BUTTON1_MASK |
- InputEvent.BUTTON2_MASK | InputEvent.BUTTON3_MASK)) != 0) {
- MenuSelectionManager.defaultManager().processMouseEvent(e);
- } else {
-
- MenuElement[] path = manager.getSelectedPath();
- if (path.length > 1 && path[path.length - 1] == menuItem) {
- MenuElement[] newPath = new MenuElement[path.length - 1];
- int i, c;
- for (i = 0, c = path.length - 1; i < c; i++) { newPath[i] = path[i]; }
- manager.setSelectedPath(newPath);
- }
- }
- }
-
- public void mouseDragged(final MouseEvent e) {
- MenuSelectionManager.defaultManager().processMouseEvent(e);
- }
-
- public void mouseMoved(final MouseEvent e) {
- }
-
- //
- // MenuDragListener
- //
- public void menuDragMouseEntered(@NotNull final MenuDragMouseEvent e) {
- MenuSelectionManager manager = e.getMenuSelectionManager();
- MenuElement[] path = e.getPath();
- manager.setSelectedPath(path);
- }
-
- public void menuDragMouseExited(final MenuDragMouseEvent e) {
- }
-
- public void menuDragMouseDragged(@NotNull final MenuDragMouseEvent e) {
- MenuSelectionManager manager = e.getMenuSelectionManager();
- MenuElement[] path = e.getPath();
- manager.setSelectedPath(path);
- }
-
- public void menuDragMouseReleased(final MenuDragMouseEvent e) {
- if (!menuItem.isEnabled()) {
- return;
- }
- MenuSelectionManager manager = e.getMenuSelectionManager();
- e.getPath();
- Point p = e.getPoint();
- if (p.x >= 0 && p.x < menuItem.getWidth() &&
- p.y >= 0 && p.y < menuItem.getHeight()) {
- doClick(manager);
- } else {
- manager.clearSelectedPath();
- }
- }
-
-
- //
- // PropertyChangeListener
- //
- public void propertyChange(final PropertyChangeEvent e) {
- String name = e.getPropertyName();
-
- if (Objects.equals(name, "labelFor") || Objects.equals(name, "displayedMnemonic") ||
- Objects.equals(name, "accelerator")) {
- updateAcceleratorBinding();
- } else if (Objects.equals(name, "text") || "font".equals(name) || "foreground".equals(name)
- || SwingUtilities2.isScaleChanged(e)) {
- // remove the old html view client property if one
- // existed, and install a new one if the text installed
- // into the JLabel is html source.
- JMenuItem lbl = ((JMenuItem) e.getSource());
- String text = lbl.getText();
- BasicHTML.updateRenderer(lbl, text);
- } else if (Objects.equals(name, "iconTextGap")) {
- defaultTextIconGap = ((Number) e.getNewValue()).intValue();
- } else if (Objects.equals(name, "horizontalTextPosition")) {
- updateCheckIcon();
- }
- }
- }
}
diff --git a/src/main/java/com/weis/darklaf/ui/menu/DarkMenuUI.java b/src/main/java/com/weis/darklaf/ui/menu/DarkMenuUI.java
index 13e84472..f8a14d70 100644
--- a/src/main/java/com/weis/darklaf/ui/menu/DarkMenuUI.java
+++ b/src/main/java/com/weis/darklaf/ui/menu/DarkMenuUI.java
@@ -1,688 +1,245 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2019 Jannis Weis
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
package com.weis.darklaf.ui.menu;
import com.weis.darklaf.util.DarkUIUtil;
-import com.weis.darklaf.util.LazyActionMap;
-import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
-import sun.swing.DefaultLookup;
-import sun.swing.UIAction;
+import sun.swing.MenuItemLayoutHelper;
+import sun.swing.SwingUtilities2;
import javax.swing.*;
-import javax.swing.event.ChangeEvent;
-import javax.swing.event.ChangeListener;
-import javax.swing.event.MenuDragMouseEvent;
-import javax.swing.event.MenuDragMouseListener;
-import javax.swing.event.MenuKeyEvent;
-import javax.swing.event.MenuKeyListener;
-import javax.swing.event.MenuListener;
-import javax.swing.event.MouseInputListener;
import javax.swing.plaf.ComponentUI;
-import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicMenuUI;
import java.awt.*;
-import java.awt.event.ActionEvent;
-import java.awt.event.KeyEvent;
-import java.awt.event.MouseEvent;
-import java.beans.PropertyChangeEvent;
-import java.beans.PropertyChangeListener;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Objects;
-/**
- * Code taken from {@link BasicMenuUI}
- */
-public class DarkMenuUI extends DarkMenuItemUIBase {
+public class DarkMenuUI extends BasicMenuUI {
- /* diagnostic aids -- should be false for production builds. */
- private static final boolean TRACE = false; // trace creates and disposes
- private static final boolean VERBOSE = false; // show reuse hits/misses
- private static final boolean DEBUG = false; // show bad params, misc.
- private static boolean crossMenuMnemonic = true;
- /**
- * The instance of {@code ChangeListener}.
- */
- protected ChangeListener changeListener;
- /**
- * The instance of {@code MenuListener}.
- */
- protected MenuListener menuListener;
- private int lastMnemonic = 0;
- /**
- * Uses as the parent of the windowInputMap when selected.
- */
- private InputMap selectedWindowInputMap;
+ protected Icon arrowIconHover;
- /**
- * Constructs a new instance of {@code BasicMenuUI}.
- *
- * @param x a component
- * @return a new instance of {@code BasicMenuUI}
- */
- @NotNull
- @Contract(value = "_ -> new", pure = true)
public static ComponentUI createUI(final JComponent x) {
return new DarkMenuUI();
}
- protected static void loadActionMap(@NotNull final LazyActionMap map) {
- loadActionMap(map);
- map.put(new Actions(Actions.SELECT, null, true));
- }
-
- private static void appendPath(@NotNull final MenuElement[] path, final MenuElement elem) {
- MenuElement[] newPath = new MenuElement[path.length + 1];
- System.arraycopy(path, 0, newPath, 0, path.length);
- newPath[path.length] = elem;
- MenuSelectionManager.defaultManager().setSelectedPath(newPath);
- }
-
- @NotNull
- protected static java.util.List getPopups() {
- MenuSelectionManager msm = MenuSelectionManager.defaultManager();
- MenuElement[] p = msm.getSelectedPath();
-
- java.util.List list = new ArrayList(p.length);
- for (MenuElement element : p) {
- if (element instanceof JPopupMenu) {
- list.add((JPopupMenu) element);
- }
- }
- return list;
- }
-
- protected static JPopupMenu getLastPopup() {
- MenuSelectionManager msm = MenuSelectionManager.defaultManager();
- MenuElement[] p = msm.getSelectedPath();
- JPopupMenu popup = null;
-
- for (int i = p.length - 1; popup == null && i >= 0; i--) {
- if (p[i] instanceof JPopupMenu) { popup = (JPopupMenu) p[i]; }
- }
- return popup;
- }
-
+ @Override
protected void installDefaults() {
super.installDefaults();
- updateDefaultBackgroundColor();
- ((JMenu) menuItem).setDelay(200);
- crossMenuMnemonic = UIManager.getBoolean("Menu.crossMenuMnemonic");
- }
-
- protected String getPropertyPrefix() {
- return "Menu";
- }
-
- protected void installListeners() {
- super.installListeners();
-
- if (changeListener == null) { changeListener = createChangeListener(menuItem); }
-
- if (changeListener != null) { menuItem.addChangeListener(changeListener); }
-
- if (menuListener == null) { menuListener = createMenuListener(menuItem); }
-
- if (menuListener != null) { ((JMenu) menuItem).addMenuListener(menuListener); }
- }
-
- protected void installKeyboardActions() {
- super.installKeyboardActions();
- updateMnemonicBinding();
- }
-
- @SuppressWarnings("deprecation")
- void updateMnemonicBinding() {
- int mnemonic = menuItem.getModel().getMnemonic();
- int[] shortcutKeys = (int[]) DefaultLookup.get(menuItem, this,
- "Menu.shortcutKeys");
- if (shortcutKeys == null) {
- shortcutKeys = new int[]{KeyEvent.ALT_MASK,
- KeyEvent.ALT_MASK | KeyEvent.ALT_GRAPH_MASK};
- }
- if (mnemonic == lastMnemonic) {
- return;
- }
- InputMap windowInputMap = SwingUtilities.getUIInputMap(
- menuItem, JComponent.WHEN_IN_FOCUSED_WINDOW);
- if (lastMnemonic != 0 && windowInputMap != null) {
- for (int shortcutKey : shortcutKeys) {
- windowInputMap.remove(KeyStroke.getKeyStroke
- (lastMnemonic, shortcutKey, false));
- }
- }
- if (mnemonic != 0) {
- if (windowInputMap == null) {
- windowInputMap = createInputMap(JComponent.
- WHEN_IN_FOCUSED_WINDOW);
- SwingUtilities.replaceUIInputMap(menuItem, JComponent.
- WHEN_IN_FOCUSED_WINDOW, windowInputMap);
- }
- for (int shortcutKey : shortcutKeys) {
- windowInputMap.put(KeyStroke.getKeyStroke(mnemonic,
- shortcutKey, false), "selectMenu");
- }
- }
- lastMnemonic = mnemonic;
- }
-
- protected void uninstallDefaults() {
- menuItem.setArmed(false);
- menuItem.setSelected(false);
- menuItem.resetKeyboardActions();
- super.uninstallDefaults();
+ acceleratorFont = UIManager.getFont("MenuItem.font");
+ acceleratorForeground = UIManager.getColor("MenuItem.foreground");
+ acceleratorSelectionForeground = UIManager.getColor("MenuItem.selectionForeground");
+ arrowIconHover = UIManager.getIcon("MenuItem.arrowHover.icon");
}
- protected void uninstallListeners() {
- super.uninstallListeners();
-
- if (changeListener != null) { menuItem.removeChangeListener(changeListener); }
-
- if (menuListener != null) { ((JMenu) menuItem).removeMenuListener(menuListener); }
-
- changeListener = null;
- menuListener = null;
- handler = null;
- }
-
- protected void uninstallKeyboardActions() {
- super.uninstallKeyboardActions();
- lastMnemonic = 0;
- }
-
- protected MouseInputListener createMouseInputListener(final JComponent c) {
- return getHandler();
- }
-
- protected DarkMenuItemUIBase.Handler getHandler() {
- if (handler == null) {
- handler = new DarkMenuUI.Handler();
- }
- return handler;
- }
-
- protected MenuDragMouseListener createMenuDragMouseListener(final JComponent c) {
- return getHandler();
- }
-
- protected MenuKeyListener createMenuKeyListener(final JComponent c) {
- return (MenuKeyListener) getHandler();
- }
-
- protected PropertyChangeListener createPropertyChangeListener(final JComponent c) {
- return getHandler();
- }
-
- public Dimension getMinimumSize(final JComponent c) {
- return (((JMenu) menuItem).isTopLevelMenu()) ?
- c.getPreferredSize() : null;
+ @Override
+ public void paint(final Graphics g, final JComponent c) {
+ paintMenuItem(g, c, checkIcon, getArrowIcon(),
+ selectionBackground, selectionForeground,
+ defaultTextIconGap);
}
- public Dimension getMaximumSize(final JComponent c) {
- if (((JMenu) menuItem).isTopLevelMenu()) {
- Dimension d = c.getPreferredSize();
- return new Dimension(d.width, Short.MAX_VALUE);
- }
- return null;
- }
-
- /**
- * Returns an instance of {@code ChangeListener}.
- *
- * @param c a component
- * @return an instance of {@code ChangeListener}
- */
- protected ChangeListener createChangeListener(final JComponent c) {
- return null;
- }
-
- /**
- * Returns an instance of {@code MenuListener}.
- *
- * @param c a component
- * @return an instance of {@code MenuListener}
- */
- protected MenuListener createMenuListener(final JComponent c) {
- return null;
+ protected Icon getArrowIcon() {
+ boolean hover = menuItem.getModel().isArmed()
+ || (menuItem instanceof JMenu && menuItem.getModel().isSelected());
+ return hover ? arrowIconHover : arrowIcon;
}
- /*
- * Set the background color depending on whether this is a toplevel menu
- * in a menubar or a submenu of another menu.
- */
- private void updateDefaultBackgroundColor() {
- if (!UIManager.getBoolean("Menu.useMenuBarBackgroundForTopLevel")) {
- return;
- }
- JMenu menu = (JMenu) menuItem;
- if (menu.getBackground() instanceof UIResource) {
- if (menu.isTopLevelMenu()) {
- menu.setBackground(UIManager.getColor("MenuBar.background"));
+ protected void paintCheckIcon(final Graphics g, @NotNull final MenuItemLayoutHelper lh,
+ final MenuItemLayoutHelper.LayoutResult lr,
+ final Color holdc, final Color foreground) {
+ if (lh.getCheckIcon() != null) {
+ ButtonModel model = lh.getMenuItem().getModel();
+ if (model.isArmed() || (lh.getMenuItem() instanceof JMenu
+ && model.isSelected())) {
+ g.setColor(foreground);
} else {
- menu.setBackground(UIManager.getColor(getPropertyPrefix() + ".background"));
+ g.setColor(holdc);
+ }
+ if (lh.useCheckAndArrow()) {
+ lh.getCheckIcon().paintIcon(lh.getMenuItem(), g,
+ lr.getCheckRect().x, lr.getCheckRect().y);
}
+ g.setColor(holdc);
}
}
- void installLazyActionMap() {
- LazyActionMap.installLazyActionMap(menuItem, BasicMenuUI.class,
- getPropertyPrefix() + ".actionMap");
- }
-
- /**
- * Sets timer to the {@code menu}.
- *
- * @param menu an instance of {@code JMenu}.
- */
- protected void setupPostTimer(@NotNull final JMenu menu) {
- Timer timer = new Timer(menu.getDelay(), new Actions(Actions.SELECT, menu, false));
- timer.setRepeats(false);
- timer.start();
- }
-
- private static class Actions extends UIAction {
- private static final String SELECT = "selectMenu";
-
- // NOTE: This will be null if the action is registered in the
- // ActionMap. For the timer use it will be non-null.
- private JMenu menu;
- private boolean force = false;
-
- Actions(final String key, final JMenu menu, final boolean shouldForce) {
- super(key);
- this.menu = menu;
- this.force = shouldForce;
- }
-
- public void actionPerformed(final ActionEvent e) {
- JMenu menu = getMenu(e);
- if (!crossMenuMnemonic) {
- JPopupMenu pm = getLastPopup();
- if (pm != null && pm != menu.getParent()) {
- return;
- }
- }
-
- final MenuSelectionManager defaultManager = MenuSelectionManager.defaultManager();
- if (force) {
- Container cnt = menu.getParent();
- if (cnt instanceof JMenuBar) {
- MenuElement[] me;
- MenuElement[] subElements;
-
- subElements = menu.getPopupMenu().getSubElements();
- if (subElements.length > 0) {
- me = new MenuElement[4];
- me[0] = (MenuElement) cnt;
- me[1] = menu;
- me[2] = menu.getPopupMenu();
- me[3] = subElements[0];
- } else {
- me = new MenuElement[3];
- me[0] = (MenuElement) cnt;
- me[1] = menu;
- me[2] = menu.getPopupMenu();
- }
- defaultManager.setSelectedPath(me);
+ protected void paintIcon(final Graphics g, @NotNull final MenuItemLayoutHelper lh,
+ final MenuItemLayoutHelper.LayoutResult lr, final Color holdc) {
+ if (lh.getIcon() != null) {
+ Icon icon;
+ ButtonModel model = lh.getMenuItem().getModel();
+ if (!model.isEnabled()) {
+ icon = lh.getMenuItem().getDisabledIcon();
+ } else if (model.isPressed() && model.isArmed()) {
+ icon = lh.getMenuItem().getPressedIcon();
+ if (icon == null) {
+ // Use default icon
+ icon = lh.getMenuItem().getIcon();
}
} else {
- MenuElement[] path = defaultManager.getSelectedPath();
- if (path.length > 0 && path[path.length - 1] == menu) {
- appendPath(path, menu.getPopupMenu());
- }
+ icon = lh.getMenuItem().getIcon();
}
- }
- private JMenu getMenu(final ActionEvent e) {
- if (e.getSource() instanceof JMenu) {
- return (JMenu) e.getSource();
+ if (icon != null) {
+ icon.paintIcon(lh.getMenuItem(), g, lr.getIconRect().x, lr.getIconRect().y);
+ g.setColor(holdc);
}
- return menu;
- }
-
- @Override
- public boolean accept(final Object c) {
- if (c instanceof JMenu) {
- return ((JMenu) c).isEnabled();
- }
- return true;
- }
- }
-
- /**
- * Instantiated and used by a menu item to handle the current menu selection
- * from mouse events. A MouseInputHandler processes and forwards all mouse events
- * to a shared instance of the MenuSelectionManager.
- *
- * This class is protected so that it can be subclassed by other look and
- * feels to implement their own mouse handling behavior. All overridden
- * methods should call the parent methods so that the menu selection
- * is correct.
- *
- * @see javax.swing.MenuSelectionManager
- * @since 1.4
- */
- protected class MouseInputHandler implements MouseInputListener {
- // NOTE: This class exists only for backward compatibility. All
- // its functionality has been moved into Handler. If you need to add
- // new functionality add it to the Handler, but make sure this
- // class calls into the Handler.
-
- public void mouseClicked(final MouseEvent e) {
- getHandler().mouseClicked(e);
- }
-
- /**
- * Invoked when the mouse has been clicked on the menu. This
- * method clears or sets the selection path of the
- * MenuSelectionManager.
- *
- * @param e the mouse event
- */
- public void mousePressed(final MouseEvent e) {
- getHandler().mousePressed(e);
- }
-
- /**
- * Invoked when the mouse has been released on the menu. Delegates the
- * mouse event to the MenuSelectionManager.
- *
- * @param e the mouse event
- */
- public void mouseReleased(final MouseEvent e) {
- getHandler().mouseReleased(e);
- }
-
- /**
- * Invoked when the cursor enters the menu. This method sets the selected
- * path for the MenuSelectionManager and handles the case
- * in which a menu item is used to pop up an additional menu, as in a
- * hierarchical menu system.
- *
- * @param e the mouse event; not used
- */
- public void mouseEntered(final MouseEvent e) {
- getHandler().mouseEntered(e);
- }
-
- public void mouseExited(final MouseEvent e) {
- getHandler().mouseExited(e);
- }
-
- /**
- * Invoked when a mouse button is pressed on the menu and then dragged.
- * Delegates the mouse event to the MenuSelectionManager.
- *
- * @param e the mouse event
- * @see java.awt.event.MouseMotionListener#mouseDragged
- */
- public void mouseDragged(final MouseEvent e) {
- getHandler().mouseDragged(e);
- }
-
- public void mouseMoved(final MouseEvent e) {
- getHandler().mouseMoved(e);
}
}
- /**
- * As of Java 2 platform 1.4, this previously undocumented class
- * is now obsolete. KeyBindings are now managed by the popup menu.
- */
- public class ChangeHandler implements ChangeListener {
- /**
- * The instance of {@code JMenu}.
- */
- public JMenu menu;
-
- /**
- * The instance of {@code BasicMenuUI}.
- */
- public BasicMenuUI ui;
-
- /**
- * {@code true} if an item of popup menu is selected.
- */
- public boolean isSelected = false;
-
- /**
- * The component that was focused.
- */
- public Component wasFocused;
-
- /**
- * Constructs a new instance of {@code ChangeHandler}.
- *
- * @param m an instance of {@code JMenu}
- * @param ui an instance of {@code BasicMenuUI}
- */
- public ChangeHandler(final JMenu m, final BasicMenuUI ui) {
- menu = m;
- this.ui = ui;
- }
-
- public void stateChanged(final ChangeEvent e) {
+ protected void paintText(final Graphics g, @NotNull final MenuItemLayoutHelper lh,
+ final MenuItemLayoutHelper.LayoutResult lr) {
+ if (!lh.getText().isBlank()) {
+ if (lh.getHtmlView() != null) {
+ // Text is HTML
+ lh.getHtmlView().paint(g, lr.getTextRect());
+ } else {
+ // Text isn't HTML
+ paintText(g, lh.getMenuItem(), lr.getTextRect(), lh.getText());
+ }
}
}
- private class Handler extends DarkMenuItemUIBase.Handler implements MenuKeyListener {
- //
- // MouseInputListener
- //
- public void mouseClicked(final MouseEvent e) {
- }
-
- /**
- * Invoked when the mouse has been clicked on the menu. This
- * method clears or sets the selection path of the
- * MenuSelectionManager.
- *
- * @param e the mouse event
- */
- public void mousePressed(final MouseEvent e) {
- JMenu menu = (JMenu) menuItem;
- if (!menu.isEnabled()) { return; }
-
- MenuSelectionManager manager = MenuSelectionManager.defaultManager();
- if (menu.isTopLevelMenu()) {
- if (menu.isSelected() && menu.getPopupMenu().isShowing()) {
- manager.clearSelectedPath();
+ protected void paintAccText(final Graphics g, final MenuItemLayoutHelper lh,
+ final MenuItemLayoutHelper.LayoutResult lr) {
+ rightAlignAccText(lh, lr);
+ if (!lh.getAccText().isBlank()) {
+ ButtonModel model = lh.getMenuItem().getModel();
+ g.setFont(lh.getAccFontMetrics().getFont());
+ if (!model.isEnabled()) {
+ // *** paint the accText disabled
+ if (disabledForeground != null) {
+ g.setColor(disabledForeground);
+ SwingUtilities2.drawString(lh.getMenuItem(), g,
+ lh.getAccText(), lr.getAccRect().x,
+ lr.getAccRect().y + lh.getAccFontMetrics().getAscent());
} else {
- Container cnt = menu.getParent();
- if (cnt instanceof JMenuBar) {
- MenuElement[] me = new MenuElement[2];
- me[0] = (MenuElement) cnt;
- me[1] = menu;
- manager.setSelectedPath(me);
- }
+ g.setColor(lh.getMenuItem().getBackground().brighter());
+ SwingUtilities2.drawString(lh.getMenuItem(), g,
+ lh.getAccText(), lr.getAccRect().x,
+ lr.getAccRect().y + lh.getAccFontMetrics().getAscent());
+ g.setColor(lh.getMenuItem().getBackground().darker());
+ SwingUtilities2.drawString(lh.getMenuItem(), g,
+ lh.getAccText(), lr.getAccRect().x - 1,
+ lr.getAccRect().y + lh.getFontMetrics().getAscent() - 1);
}
- }
-
- MenuElement[] selectedPath = manager.getSelectedPath();
- if (selectedPath.length > 0 && selectedPath[selectedPath.length - 1] != menu.getPopupMenu()) {
- if (menu.isTopLevelMenu() || menu.getDelay() == 0) {
- appendPath(selectedPath, menu.getPopupMenu());
+ } else {
+ // *** paint the accText normally
+ if (model.isArmed()
+ || (lh.getMenuItem() instanceof JMenu
+ && model.isSelected())) {
+ g.setColor(acceleratorSelectionForeground);
} else {
- setupPostTimer(menu);
+ g.setColor(acceleratorForeground);
}
+ SwingUtilities2.drawString(lh.getMenuItem(), g, lh.getAccText(),
+ lr.getAccRect().x, lr.getAccRect().y +
+ lh.getAccFontMetrics().getAscent());
}
}
+ }
- /**
- * Invoked when the mouse has been released on the menu. Delegates the
- * mouse event to the MenuSelectionManager.
- *
- * @param e the mouse event
- */
- public void mouseReleased(final MouseEvent e) {
- JMenu menu = (JMenu) menuItem;
- if (!menu.isEnabled()) { return; }
- MenuSelectionManager manager = MenuSelectionManager.defaultManager();
- manager.processMouseEvent(e);
- if (!e.isConsumed()) { manager.clearSelectedPath(); }
- }
-
- /**
- * Invoked when the cursor enters the menu. This method sets the selected
- * path for the MenuSelectionManager and handles the case
- * in which a menu item is used to pop up an additional menu, as in a
- * hierarchical menu system.
- *
- * @param e the mouse event; not used
- */
- public void mouseEntered(final MouseEvent e) {
- JMenu menu = (JMenu) menuItem;
- // only disable the menu highlighting if it's disabled and the property isn't
- // true. This allows disabled rollovers to work in WinL&F
- if (!menu.isEnabled() && !UIManager.getBoolean("MenuItem.disabledAreNavigable")) {
- return;
+ protected void paintArrowIcon(final Graphics g, @NotNull final MenuItemLayoutHelper lh,
+ final MenuItemLayoutHelper.LayoutResult lr,
+ final Color foreground) {
+ if (lh.getArrowIcon() != null) {
+ ButtonModel model = lh.getMenuItem().getModel();
+ if (model.isArmed() || (lh.getMenuItem() instanceof JMenu
+ && model.isSelected())) {
+ g.setColor(foreground);
}
-
- MenuSelectionManager manager = MenuSelectionManager.defaultManager();
- MenuElement[] selectedPath = manager.getSelectedPath();
- if (!menu.isTopLevelMenu()) {
- if (!(selectedPath.length > 0 && selectedPath[selectedPath.length - 1] == menu.getPopupMenu())) {
- if (menu.getDelay() == 0) {
- appendPath(getPath(), menu.getPopupMenu());
- } else {
- manager.setSelectedPath(getPath());
- setupPostTimer(menu);
- }
- }
- } else {
- if (selectedPath.length > 0 && selectedPath[0] == menu.getParent()) {
- MenuElement[] newPath = new MenuElement[3];
- // A top level menu's parent is by definition
- // a JMenuBar
- newPath[0] = (MenuElement) menu.getParent();
- newPath[1] = menu;
- if (getLastPopup() != null) {
- newPath[2] = menu.getPopupMenu();
- }
- manager.setSelectedPath(newPath);
- }
+ if (lh.useCheckAndArrow()) {
+ lh.getArrowIcon().paintIcon(lh.getMenuItem(), g,
+ lr.getArrowRect().x, lr.getArrowRect().y);
}
}
+ }
- public void mouseExited(final MouseEvent e) {
- }
-
- /**
- * Invoked when a mouse button is pressed on the menu and then dragged.
- * Delegates the mouse event to the MenuSelectionManager.
- *
- * @param e the mouse event
- * @see java.awt.event.MouseMotionListener#mouseDragged
- */
- public void mouseDragged(final MouseEvent e) {
- JMenu menu = (JMenu) menuItem;
- if (!menu.isEnabled()) { return; }
- MenuSelectionManager.defaultManager().processMouseEvent(e);
+ private static void rightAlignAccText(@NotNull final MenuItemLayoutHelper lh,
+ @NotNull final MenuItemLayoutHelper.LayoutResult lr) {
+ var accRect = lr.getAccRect();
+ ButtonModel model = lh.getMenuItem().getModel();
+ if (model.isEnabled()) {
+ accRect.x = lh.getViewRect().x + lh.getViewRect().width
+ - lh.getMenuItem().getIconTextGap() - lr.getAccRect().width;
}
+ }
- public void mouseMoved(final MouseEvent e) {
- }
+ protected void paintMenuItem(@NotNull final Graphics g, final JComponent c,
+ final Icon checkIcon, final Icon arrowIcon,
+ final Color background, final Color foreground,
+ final int defaultTextIconGap) {
+ // Save original graphics font and color
+ Font holdf = g.getFont();
+ Color holdc = g.getColor();
- //
- // MenuDragHandler
- //
- public void menuDragMouseEntered(final MenuDragMouseEvent e) {
- }
+ JMenuItem mi = (JMenuItem) c;
+ g.setFont(mi.getFont());
- public void menuDragMouseDragged(final MenuDragMouseEvent e) {
- if (!menuItem.isEnabled()) return;
+ Rectangle viewRect = new Rectangle(0, 0, mi.getWidth(), mi.getHeight());
+ DarkUIUtil.applyInsets(viewRect, mi.getInsets());
- MenuSelectionManager manager = e.getMenuSelectionManager();
- MenuElement[] path = e.getPath();
+ MenuItemLayoutHelper lh = new MenuItemLayoutHelper(mi, checkIcon,
+ arrowIcon, viewRect, defaultTextIconGap, acceleratorDelimiter,
+ mi.getComponentOrientation().isLeftToRight(), mi.getFont(),
+ acceleratorFont, MenuItemLayoutHelper.useCheckAndArrow(menuItem),
+ getPropertyPrefix());
+ MenuItemLayoutHelper.LayoutResult lr = lh.layoutMenuItem();
- Point p = e.getPoint();
- if (p.x >= 0 && p.x < menuItem.getWidth() &&
- p.y >= 0 && p.y < menuItem.getHeight()) {
- JMenu menu = (JMenu) menuItem;
- MenuElement[] selectedPath = manager.getSelectedPath();
- if (!(selectedPath.length > 0 &&
- selectedPath[selectedPath.length - 1] ==
- menu.getPopupMenu())) {
- if (menu.isTopLevelMenu() ||
- menu.getDelay() == 0 ||
- e.getID() == MouseEvent.MOUSE_DRAGGED) {
- appendPath(path, menu.getPopupMenu());
- } else {
- manager.setSelectedPath(path);
- setupPostTimer(menu);
- }
- }
- } else if (e.getID() == MouseEvent.MOUSE_RELEASED) {
- Component comp = manager.componentForPoint(e.getComponent(), e.getPoint());
- if (comp == null) { manager.clearSelectedPath(); }
- }
-
- }
+ paintBackground(g, mi, background);
+ paintCheckIcon(g, lh, lr, holdc, foreground);
+ paintIcon(g, lh, lr, holdc);
+ g.setColor(foreground);
+ paintText(g, lh, lr);
+ paintAccText(g, lh, lr);
+ paintArrowIcon(g, lh, lr, foreground);
- public void menuDragMouseExited(final MenuDragMouseEvent e) {
- }
+ // Restore original graphics font and color
+ g.setColor(holdc);
+ g.setFont(holdf);
+ }
- public void menuDragMouseReleased(final MenuDragMouseEvent e) {
- }
+ @Override
+ protected void paintBackground(@NotNull final Graphics g, @NotNull final JMenuItem menuItem, final Color bgColor) {
+ ButtonModel model = menuItem.getModel();
+ Color oldColor = g.getColor();
+ int menuWidth = menuItem.getWidth();
+ int menuHeight = menuItem.getHeight() + 1;
- //
- // PropertyChangeListener
- //
- public void propertyChange(final PropertyChangeEvent e) {
- if (Objects.equals(e.getPropertyName(), AbstractButton.
- MNEMONIC_CHANGED_PROPERTY)) {
- updateMnemonicBinding();
+ boolean parentOpaque = menuItem.getParent().isOpaque();
+ if (menuItem.isOpaque() && parentOpaque) {
+ if (model.isArmed() || (menuItem instanceof JMenu && model.isSelected())) {
+ g.setColor(bgColor);
+ g.fillRect(0, 0, menuWidth, menuHeight);
} else {
- if (e.getPropertyName().equals("ancestor")) {
- updateDefaultBackgroundColor();
- }
- super.propertyChange(e);
- }
- }
-
- //
- // MenuKeyListener
- //
-
- /**
- * Open the Menu
- */
- public void menuKeyTyped(final MenuKeyEvent e) {
- if (!crossMenuMnemonic && getLastPopup() != null) {
- // when crossMenuMnemonic is not set, we don't open a toplevel
- // menu if another toplevel menu is already open
- return;
- }
-
- if (getPopups().size() != 0) {
- //Fix 6939261: to return in case not on the main menu
- //and has a pop-up.
- //after return code will be handled in BasicPopupMenuUI.java
- return;
- }
-
- char key = Character.toLowerCase((char) menuItem.getMnemonic());
- MenuElement[] path = e.getPath();
- if (key == Character.toLowerCase(e.getKeyChar())) {
- JPopupMenu popupMenu = ((JMenu) menuItem).getPopupMenu();
- ArrayList newList = new ArrayList<>(Arrays.asList(path));
- newList.add(popupMenu);
- MenuElement[] subs = popupMenu.getSubElements();
- MenuElement sub = DarkUIUtil.findEnabledChild(subs, -1, true);
- if (sub != null) {
- newList.add(sub);
- }
- MenuSelectionManager manager = e.getMenuSelectionManager();
- MenuElement[] newPath = new MenuElement[0];
- newPath = newList.toArray(newPath);
- manager.setSelectedPath(newPath);
- e.consume();
- }
- }
-
- public void menuKeyPressed(final MenuKeyEvent e) {
- }
-
- public void menuKeyReleased(final MenuKeyEvent e) {
+ g.setColor(menuItem.getBackground());
+ g.fillRect(0, 0, menuWidth, menuHeight);
+ }
+ g.setColor(oldColor);
+ } else if (model.isArmed() || (menuItem instanceof JMenu &&
+ model.isSelected())) {
+ g.setColor(bgColor);
+ g.fillRect(0, 0, menuWidth, menuHeight);
+ g.setColor(oldColor);
}
}
}
diff --git a/src/main/java/com/weis/darklaf/ui/scrollpane/DarkScrollBarUI.java b/src/main/java/com/weis/darklaf/ui/scrollpane/DarkScrollBarUI.java
index 885f89b7..2b48b3a0 100644
--- a/src/main/java/com/weis/darklaf/ui/scrollpane/DarkScrollBarUI.java
+++ b/src/main/java/com/weis/darklaf/ui/scrollpane/DarkScrollBarUI.java
@@ -28,7 +28,6 @@ import com.weis.darklaf.util.Animator;
import com.weis.darklaf.util.DarkUIUtil;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
@@ -71,9 +70,13 @@ public class DarkScrollBarUI extends BasicScrollBarUI {
if (scrollbar.getOrientation() == VERTICAL && !e.isShiftDown()
|| scrollbar.getOrientation() == HORIZONTAL && e.isShiftDown()) {
scrollbar.setValueIsAdjusting(true);
+ var sp = scrollbar.getClientProperty("JScrollBar.scrollPaneParent");
if (scrollbar.getParent() instanceof JScrollPane) {
doScroll(scrollbar, ((JScrollPane) scrollbar.getParent()).getViewport(), e,
scrollbar.getParent().getComponentOrientation().isLeftToRight());
+ } else if (sp instanceof JScrollPane) {
+ doScroll(scrollbar, ((JScrollPane) sp).getViewport(), e,
+ scrollbar.getParent().getComponentOrientation().isLeftToRight());
} else {
doScroll(scrollbar, null, e, scrollbar.getComponentOrientation().isLeftToRight());
}
@@ -88,18 +91,15 @@ public class DarkScrollBarUI extends BasicScrollBarUI {
private Animator thumbFadeinAnimator;
private boolean mouseOverTrack = false;
private boolean mouseOverThumb = false;
- private final MouseMotionListener mouseMotionListener = new MouseMovementListener() {
- @Override
- public void mouseMoved(@Nullable final MouseEvent e) {
- if (e == null) {
- return;
- }
- boolean overThumb = isOverThumb(e.getPoint());
- if (overThumb != mouseOverThumb) {
- mouseOverThumb = overThumb;
- if (!scrollbar.getValueIsAdjusting()) {
- resetThumbAnimator();
- }
+ private final MouseMotionListener mouseMotionListener = (MouseMovementListener) e -> {
+ if (e == null) {
+ return;
+ }
+ boolean overThumb = isOverThumb(e.getPoint());
+ if (overThumb != mouseOverThumb) {
+ mouseOverThumb = overThumb;
+ if (!scrollbar.getValueIsAdjusting()) {
+ resetThumbAnimator();
}
}
};
diff --git a/src/main/java/com/weis/darklaf/ui/tabframe/DarkTabFrameTabLabelUI.java b/src/main/java/com/weis/darklaf/ui/tabframe/DarkTabFrameTabLabelUI.java
index 1c2452ff..91155bc0 100644
--- a/src/main/java/com/weis/darklaf/ui/tabframe/DarkTabFrameTabLabelUI.java
+++ b/src/main/java/com/weis/darklaf/ui/tabframe/DarkTabFrameTabLabelUI.java
@@ -151,7 +151,6 @@ public class DarkTabFrameTabLabelUI extends DarkLabelUI implements PropertyChang
if (tabFrame == null) return;
int acc = tabComponent.getAccelerator();
if (acc < 0) return;
- System.out.println("install");
tabFrame.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT)
.put(KeyStroke.getKeyStroke(UIManager.getString("TabFrame.acceleratorKeyCode") + " " + acc),
"accelerator_" + acc);
diff --git a/src/main/java/com/weis/darklaf/ui/table/BasicTableUIBridge.java b/src/main/java/com/weis/darklaf/ui/table/BasicTableUIBridge.java
new file mode 100644
index 00000000..045832f6
--- /dev/null
+++ b/src/main/java/com/weis/darklaf/ui/table/BasicTableUIBridge.java
@@ -0,0 +1,2189 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2019 Jannis Weis
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+package com.weis.darklaf.ui.table;
+
+import com.weis.darklaf.util.DarkUIUtil;
+import com.weis.darklaf.util.LazyActionMap;
+import org.jdesktop.swingx.plaf.basic.core.BasicTransferable;
+import org.jdesktop.swingx.plaf.basic.core.DragRecognitionSupport;
+import sun.swing.DefaultLookup;
+import sun.swing.SwingUtilities2;
+import sun.swing.UIAction;
+
+import javax.swing.*;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+import javax.swing.event.MouseInputListener;
+import javax.swing.plaf.ComponentUI;
+import javax.swing.plaf.TableHeaderUI;
+import javax.swing.plaf.UIResource;
+import javax.swing.plaf.basic.BasicTableUI;
+import javax.swing.table.DefaultTableCellRenderer;
+import javax.swing.table.JTableHeader;
+import javax.swing.table.TableCellEditor;
+import javax.swing.table.TableCellRenderer;
+import javax.swing.table.TableColumn;
+import javax.swing.table.TableColumnModel;
+import java.awt.*;
+import java.awt.datatransfer.Transferable;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.FocusEvent;
+import java.awt.event.FocusListener;
+import java.awt.event.KeyEvent;
+import java.awt.event.KeyListener;
+import java.awt.event.MouseEvent;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.util.Enumeration;
+
+public class BasicTableUIBridge extends BasicTableUI {
+ protected static final StringBuilder BASELINE_COMPONENT_KEY =
+ new StringBuilder("Table.baselineComponent");
+
+//
+// Instance Variables
+//
+
+ // The JTable that is delegating the painting to this UI.
+ protected static final TransferHandler defaultTransferHandler = new TableTransferHandler();
+ /**
+ * The instance of {@code JTable}.
+ */
+ protected JTable table;
+ /**
+ * The instance of {@code CellRendererPane}.
+ */
+ protected CellRendererPane rendererPane;
+ /**
+ * {@code KeyListener} that are attached to the {@code JTable}.
+ */
+ protected KeyListener keyListener;
+ /**
+ * {@code FocusListener} that are attached to the {@code JTable}.
+ */
+ protected FocusListener focusListener;
+ /**
+ * {@code MouseInputListener} that are attached to the {@code JTable}.
+ */
+ protected MouseInputListener mouseInputListener;
+ protected Handler handler;
+
+//
+// Helper class for keyboard actions
+//
+ /**
+ * Local cache of Table's client property "Table.isFileList"
+ */
+ protected boolean isFileList = false;
+
+
+//
+// The Table's Key listener
+//
+
+ /**
+ * Returns a new instance of {@code BasicTableUI}.
+ *
+ * @param c a component
+ * @return a new instance of {@code BasicTableUI}
+ */
+ public static ComponentUI createUI(final JComponent c) {
+ return new BasicTableUI();
+ }
+
+//
+// The Table's focus listener
+//
+
+ protected static int getAdjustedLead(final JTable table, final boolean row) {
+ return row ? getAdjustedLead(table, row, table.getSelectionModel())
+ : getAdjustedLead(table, row, table.getColumnModel().getSelectionModel());
+ }
+
+//
+// The Table's mouse and mouse motion listeners
+//
+
+ protected static int getAdjustedLead(final JTable table,
+ final boolean row,
+ final ListSelectionModel model) {
+
+ int index = model.getLeadSelectionIndex();
+ int compare = row ? table.getRowCount() : table.getColumnCount();
+ return index < compare ? index : -1;
+ }
+
+ /*
+ * Returns true if the given point is outside the preferredSize of the
+ * item at the given row of the table. (Column must be 0).
+ * Returns false if the "Table.isFileList" client property is not set.
+ */
+ protected boolean pointOutsidePrefSize(final int row, final int column, final Point p) {
+ if (!isFileList) {
+ return false;
+ }
+
+ return SwingUtilities2.pointOutsidePrefSize(table, row, column, p);
+ }
+
+ protected static class Actions extends UIAction {
+ protected static final String CANCEL_EDITING = "cancel";
+ protected static final String SELECT_ALL = "selectAll";
+ protected static final String CLEAR_SELECTION = "clearSelection";
+ protected static final String START_EDITING = "startEditing";
+
+ protected static final String NEXT_ROW = "selectNextRow";
+ protected static final String NEXT_ROW_CELL = "selectNextRowCell";
+ protected static final String NEXT_ROW_EXTEND_SELECTION =
+ "selectNextRowExtendSelection";
+ protected static final String NEXT_ROW_CHANGE_LEAD =
+ "selectNextRowChangeLead";
+ protected static final String PREVIOUS_ROW = "selectPreviousRow";
+ protected static final String PREVIOUS_ROW_CELL = "selectPreviousRowCell";
+ protected static final String PREVIOUS_ROW_EXTEND_SELECTION =
+ "selectPreviousRowExtendSelection";
+ protected static final String PREVIOUS_ROW_CHANGE_LEAD =
+ "selectPreviousRowChangeLead";
+
+ protected static final String NEXT_COLUMN = "selectNextColumn";
+ protected static final String NEXT_COLUMN_CELL = "selectNextColumnCell";
+ protected static final String NEXT_COLUMN_EXTEND_SELECTION =
+ "selectNextColumnExtendSelection";
+ protected static final String NEXT_COLUMN_CHANGE_LEAD =
+ "selectNextColumnChangeLead";
+ protected static final String PREVIOUS_COLUMN = "selectPreviousColumn";
+ protected static final String PREVIOUS_COLUMN_CELL =
+ "selectPreviousColumnCell";
+ protected static final String PREVIOUS_COLUMN_EXTEND_SELECTION =
+ "selectPreviousColumnExtendSelection";
+ protected static final String PREVIOUS_COLUMN_CHANGE_LEAD =
+ "selectPreviousColumnChangeLead";
+
+ protected static final String SCROLL_LEFT_CHANGE_SELECTION =
+ "scrollLeftChangeSelection";
+ protected static final String SCROLL_LEFT_EXTEND_SELECTION =
+ "scrollLeftExtendSelection";
+ protected static final String SCROLL_RIGHT_CHANGE_SELECTION =
+ "scrollRightChangeSelection";
+ protected static final String SCROLL_RIGHT_EXTEND_SELECTION =
+ "scrollRightExtendSelection";
+
+ protected static final String SCROLL_UP_CHANGE_SELECTION =
+ "scrollUpChangeSelection";
+ protected static final String SCROLL_UP_EXTEND_SELECTION =
+ "scrollUpExtendSelection";
+ protected static final String SCROLL_DOWN_CHANGE_SELECTION =
+ "scrollDownChangeSelection";
+ protected static final String SCROLL_DOWN_EXTEND_SELECTION =
+ "scrollDownExtendSelection";
+
+ protected static final String FIRST_COLUMN =
+ "selectFirstColumn";
+ protected static final String FIRST_COLUMN_EXTEND_SELECTION =
+ "selectFirstColumnExtendSelection";
+ protected static final String LAST_COLUMN =
+ "selectLastColumn";
+ protected static final String LAST_COLUMN_EXTEND_SELECTION =
+ "selectLastColumnExtendSelection";
+
+ protected static final String FIRST_ROW =
+ "selectFirstRow";
+ protected static final String FIRST_ROW_EXTEND_SELECTION =
+ "selectFirstRowExtendSelection";
+ protected static final String LAST_ROW =
+ "selectLastRow";
+ protected static final String LAST_ROW_EXTEND_SELECTION =
+ "selectLastRowExtendSelection";
+
+ // add the lead item to the selection without changing lead or anchor
+ protected static final String ADD_TO_SELECTION = "addToSelection";
+
+ // toggle the selected state of the lead item and move the anchor to it
+ protected static final String TOGGLE_AND_ANCHOR = "toggleAndAnchor";
+
+ // extend the selection to the lead item
+ protected static final String EXTEND_TO = "extendTo";
+
+ // move the anchor to the lead and ensure only that item is selected
+ protected static final String MOVE_SELECTION_TO = "moveSelectionTo";
+
+ // give focus to the JTableHeader, if one exists
+ protected static final String FOCUS_HEADER = "focusHeader";
+
+ protected int dx;
+ protected int dy;
+ protected boolean extend;
+ protected boolean inSelection;
+
+ // horizontally, forwards always means right,
+ // regardless of component orientation
+ protected boolean forwards;
+ protected boolean vertically;
+ protected boolean toLimit;
+
+ protected int leadRow;
+ protected int leadColumn;
+
+ Actions(final String name) {
+ super(name);
+ }
+
+ Actions(final String name, final boolean extend, final boolean forwards,
+ final boolean vertically, final boolean toLimit) {
+ this(name, 0, 0, extend, false);
+ this.forwards = forwards;
+ this.vertically = vertically;
+ this.toLimit = toLimit;
+ }
+
+ Actions(final String name, int dx, int dy, final boolean extend,
+ final boolean inSelection) {
+ super(name);
+
+ // Actions spcifying true for "inSelection" are
+ // fairly sensitive to bad parameter values. They require
+ // that one of dx and dy be 0 and the other be -1 or 1.
+ // Bogus parameter values could cause an infinite loop.
+ // To prevent any problems we massage the params here
+ // and complain if we get something we can't deal with.
+ if (inSelection) {
+ this.inSelection = true;
+
+ // look at the sign of dx and dy only
+ dx = sign(dx);
+ dy = sign(dy);
+
+ // make sure one is zero, but not both
+ assert (dx == 0 || dy == 0) && !(dx == 0 && dy == 0);
+ }
+
+ this.dx = dx;
+ this.dy = dy;
+ this.extend = extend;
+ }
+
+ protected static int sign(final int num) {
+ return Integer.compare(num, 0);
+ }
+
+ public void actionPerformed(final ActionEvent e) {
+ String key = getName();
+ JTable table = (JTable) e.getSource();
+
+ ListSelectionModel rsm = table.getSelectionModel();
+ leadRow = getAdjustedLead(table, true, rsm);
+
+ ListSelectionModel csm = table.getColumnModel().getSelectionModel();
+ leadColumn = getAdjustedLead(table, false, csm);
+
+ if (key.equals(SCROLL_LEFT_CHANGE_SELECTION) || // Paging Actions
+ key.equals(SCROLL_LEFT_EXTEND_SELECTION) ||
+ key.equals(SCROLL_RIGHT_CHANGE_SELECTION) ||
+ key.equals(SCROLL_RIGHT_EXTEND_SELECTION) ||
+ key.equals(SCROLL_UP_CHANGE_SELECTION) ||
+ key.equals(SCROLL_UP_EXTEND_SELECTION) ||
+ key.equals(SCROLL_DOWN_CHANGE_SELECTION) ||
+ key.equals(SCROLL_DOWN_EXTEND_SELECTION) ||
+ key.equals(FIRST_COLUMN) ||
+ key.equals(FIRST_COLUMN_EXTEND_SELECTION) ||
+ key.equals(FIRST_ROW) ||
+ key.equals(FIRST_ROW_EXTEND_SELECTION) ||
+ key.equals(LAST_COLUMN) ||
+ key.equals(LAST_COLUMN_EXTEND_SELECTION) ||
+ key.equals(LAST_ROW) ||
+ key.equals(LAST_ROW_EXTEND_SELECTION)) {
+ if (toLimit) {
+ if (vertically) {
+ int rowCount = table.getRowCount();
+ this.dx = 0;
+ this.dy = forwards ? rowCount : -rowCount;
+ } else {
+ int colCount = table.getColumnCount();
+ this.dx = forwards ? colCount : -colCount;
+ this.dy = 0;
+ }
+ } else {
+ if (!(SwingUtilities.getUnwrappedParent(table).getParent() instanceof
+ JScrollPane)) {
+ return;
+ }
+
+ Dimension delta = table.getParent().getSize();
+
+ if (vertically) {
+ Rectangle r = table.getCellRect(leadRow, 0, true);
+ if (forwards) {
+ // scroll by at least one cell
+ r.y += Math.max(delta.height, r.height);
+ } else {
+ r.y -= delta.height;
+ }
+
+ this.dx = 0;
+ int newRow = table.rowAtPoint(r.getLocation());
+ if (newRow == -1 && forwards) {
+ newRow = table.getRowCount();
+ }
+ this.dy = newRow - leadRow;
+ } else {
+ Rectangle r = table.getCellRect(0, leadColumn, true);
+
+ if (forwards) {
+ // scroll by at least one cell
+ r.x += Math.max(delta.width, r.width);
+ } else {
+ r.x -= delta.width;
+ }
+
+ int newColumn = table.columnAtPoint(r.getLocation());
+ if (newColumn == -1) {
+ boolean ltr = table.getComponentOrientation().isLeftToRight();
+
+ newColumn = forwards ? (ltr ? table.getColumnCount() : 0)
+ : (ltr ? 0 : table.getColumnCount());
+
+ }
+ this.dx = newColumn - leadColumn;
+ this.dy = 0;
+ }
+ }
+ }
+ switch (key) {
+ case NEXT_ROW:
+ case NEXT_ROW_CELL:
+ case NEXT_ROW_EXTEND_SELECTION:
+ case NEXT_ROW_CHANGE_LEAD:
+ case NEXT_COLUMN:
+ case NEXT_COLUMN_CELL:
+ case NEXT_COLUMN_EXTEND_SELECTION:
+ case NEXT_COLUMN_CHANGE_LEAD:
+ case PREVIOUS_ROW:
+ case PREVIOUS_ROW_CELL:
+ case PREVIOUS_ROW_EXTEND_SELECTION:
+ case PREVIOUS_ROW_CHANGE_LEAD:
+ case PREVIOUS_COLUMN:
+ case PREVIOUS_COLUMN_CELL:
+ case PREVIOUS_COLUMN_EXTEND_SELECTION:
+ case PREVIOUS_COLUMN_CHANGE_LEAD:
+ case SCROLL_LEFT_CHANGE_SELECTION:
+ case SCROLL_LEFT_EXTEND_SELECTION:
+ case SCROLL_RIGHT_CHANGE_SELECTION:
+ case SCROLL_RIGHT_EXTEND_SELECTION:
+ case SCROLL_UP_CHANGE_SELECTION:
+ case SCROLL_UP_EXTEND_SELECTION:
+ case SCROLL_DOWN_CHANGE_SELECTION:
+ case SCROLL_DOWN_EXTEND_SELECTION:
+ case FIRST_COLUMN:
+ case FIRST_COLUMN_EXTEND_SELECTION:
+ case FIRST_ROW:
+ case FIRST_ROW_EXTEND_SELECTION:
+ case LAST_COLUMN:
+ case LAST_COLUMN_EXTEND_SELECTION:
+ case LAST_ROW:
+ case LAST_ROW_EXTEND_SELECTION:
+
+ if (table.isEditing() &&
+ !table.getCellEditor().stopCellEditing()) {
+ return;
+ }
+
+ // Unfortunately, this strategy introduces bugs because
+ // of the asynchronous nature of requestFocus() call below.
+ // Introducing a delay with invokeLater() makes this work
+ // in the typical case though race conditions then allow
+ // focus to disappear altogether. The right solution appears
+ // to be to fix requestFocus() so that it queues a request
+ // for the focus regardless of who owns the focus at the
+ // time the call to requestFocus() is made. The optimisation
+ // to ignore the call to requestFocus() when the component
+ // already has focus may ligitimately be made as the
+ // request focus event is dequeued, not before.
+
+ // boolean wasEditingWithFocus = table.isEditing() &&
+ // table.getEditorComponent().isFocusOwner();
+
+ boolean changeLead = false;
+ if (key.equals(NEXT_ROW_CHANGE_LEAD) || key.equals(PREVIOUS_ROW_CHANGE_LEAD)) {
+ changeLead = (rsm.getSelectionMode()
+ == ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
+ } else if (key.equals(NEXT_COLUMN_CHANGE_LEAD) || key.equals(PREVIOUS_COLUMN_CHANGE_LEAD)) {
+ changeLead = (csm.getSelectionMode()
+ == ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
+ }
+
+ if (changeLead) {
+ moveWithinTableRange(table, dx, dy);
+ if (dy != 0) {
+ // casting should be safe since the action is only enabled
+ // for DefaultListSelectionModel
+ ((DefaultListSelectionModel) rsm).moveLeadSelectionIndex(leadRow);
+ if (getAdjustedLead(table, false, csm) == -1
+ && table.getColumnCount() > 0) {
+
+ ((DefaultListSelectionModel) csm).moveLeadSelectionIndex(0);
+ }
+ } else {
+ // casting should be safe since the action is only enabled
+ // for DefaultListSelectionModel
+ ((DefaultListSelectionModel) csm).moveLeadSelectionIndex(leadColumn);
+ if (getAdjustedLead(table, true, rsm) == -1
+ && table.getRowCount() > 0) {
+
+ ((DefaultListSelectionModel) rsm).moveLeadSelectionIndex(0);
+ }
+ }
+
+ Rectangle cellRect = table.getCellRect(leadRow, leadColumn, false);
+ if (cellRect != null) {
+ table.scrollRectToVisible(cellRect);
+ }
+ } else if (!inSelection) {
+ moveWithinTableRange(table, dx, dy);
+ table.changeSelection(leadRow, leadColumn, false, extend);
+ } else {
+ if (table.getRowCount() <= 0 || table.getColumnCount() <= 0) {
+ // bail - don't try to move selection on an empty table
+ return;
+ }
+
+ if (moveWithinSelectedRange(table, dx, dy, rsm, csm)) {
+ // this is the only way we have to set both the lead
+ // and the anchor without changing the selection
+ if (rsm.isSelectedIndex(leadRow)) {
+ rsm.addSelectionInterval(leadRow, leadRow);
+ } else {
+ rsm.removeSelectionInterval(leadRow, leadRow);
+ }
+
+ if (csm.isSelectedIndex(leadColumn)) {
+ csm.addSelectionInterval(leadColumn, leadColumn);
+ } else {
+ csm.removeSelectionInterval(leadColumn, leadColumn);
+ }
+
+ Rectangle cellRect = table.getCellRect(leadRow, leadColumn, false);
+ if (cellRect != null) {
+ table.scrollRectToVisible(cellRect);
+ }
+ } else {
+ table.changeSelection(leadRow, leadColumn,
+ false, false);
+ }
+ }
+
+ /*
+ if (wasEditingWithFocus) {
+ table.editCellAt(leadRow, leadColumn);
+ final Component editorComp = table.getEditorComponent();
+ if (editorComp != null) {
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ editorComp.requestFocus();
+ }
+ });
+ }
+ }
+ */
+ break;
+ case CANCEL_EDITING:
+ table.removeEditor();
+ break;
+ case SELECT_ALL:
+ table.selectAll();
+ break;
+ case CLEAR_SELECTION:
+ table.clearSelection();
+ break;
+ case START_EDITING:
+ if (!table.hasFocus()) {
+ CellEditor cellEditor = table.getCellEditor();
+ if (cellEditor != null && !cellEditor.stopCellEditing()) {
+ return;
+ }
+ table.requestFocus();
+ return;
+ }
+ table.editCellAt(leadRow, leadColumn, e);
+ Component editorComp = table.getEditorComponent();
+ if (editorComp != null) {
+ editorComp.requestFocus();
+ }
+ break;
+ case ADD_TO_SELECTION:
+ if (!table.isCellSelected(leadRow, leadColumn)) {
+ int oldAnchorRow = rsm.getAnchorSelectionIndex();
+ int oldAnchorColumn = csm.getAnchorSelectionIndex();
+ rsm.setValueIsAdjusting(true);
+ csm.setValueIsAdjusting(true);
+ table.changeSelection(leadRow, leadColumn, true, false);
+ rsm.setAnchorSelectionIndex(oldAnchorRow);
+ csm.setAnchorSelectionIndex(oldAnchorColumn);
+ rsm.setValueIsAdjusting(false);
+ csm.setValueIsAdjusting(false);
+ }
+ break;
+ case TOGGLE_AND_ANCHOR:
+ table.changeSelection(leadRow, leadColumn, true, false);
+ break;
+ case EXTEND_TO:
+ table.changeSelection(leadRow, leadColumn, false, true);
+ break;
+ case MOVE_SELECTION_TO:
+ table.changeSelection(leadRow, leadColumn, false, false);
+ break;
+ case FOCUS_HEADER:
+ JTableHeader th = table.getTableHeader();
+ if (th != null) {
+ //Set the header's selected column to match the table.
+ int col = table.getSelectedColumn();
+ if (col >= 0) {
+ TableHeaderUI thUI = th.getUI();
+ if (thUI instanceof DarkTableHeaderUI) {
+ ((DarkTableHeaderUI) thUI).selectColumn(col);
+ }
+ }
+
+ //Then give the header the focus.
+ th.requestFocusInWindow();
+ }
+ break;
+ }
+ }
+
+ protected void moveWithinTableRange(final JTable table, final int dx, final int dy) {
+ leadRow = clipToRange(leadRow + dy, 0, table.getRowCount());
+ leadColumn = clipToRange(leadColumn + dx, 0, table.getColumnCount());
+ }
+
+ /**
+ * Called to move within the selected range of the given JTable.
+ * This method uses the table's notion of selection, which is
+ * important to allow the user to navigate between items visually
+ * selected on screen. This notion may or may not be the same as
+ * what could be determined by directly querying the selection models.
+ * It depends on certain table properties (such as whether or not
+ * row or column selection is allowed). When performing modifications,
+ * it is recommended that caution be taken in order to preserve
+ * the intent of this method, especially when deciding whether to
+ * query the selection models or interact with JTable directly.
+ */
+ protected boolean moveWithinSelectedRange(final JTable table, final int dx, final int dy,
+ final ListSelectionModel rsm, final ListSelectionModel csm) {
+
+ // Note: The Actions constructor ensures that only one of
+ // dx and dy is 0, and the other is either -1 or 1
+
+ // find out how many items the table is showing as selected
+ // and the range of items to navigate through
+ int totalCount;
+ int minX, maxX, minY, maxY;
+
+ boolean rs = table.getRowSelectionAllowed();
+ boolean cs = table.getColumnSelectionAllowed();
+
+ // both column and row selection
+ if (rs && cs) {
+ totalCount = table.getSelectedRowCount() * table.getSelectedColumnCount();
+ minX = csm.getMinSelectionIndex();
+ maxX = csm.getMaxSelectionIndex();
+ minY = rsm.getMinSelectionIndex();
+ maxY = rsm.getMaxSelectionIndex();
+ // row selection only
+ } else if (rs) {
+ totalCount = table.getSelectedRowCount();
+ minX = 0;
+ maxX = table.getColumnCount() - 1;
+ minY = rsm.getMinSelectionIndex();
+ maxY = rsm.getMaxSelectionIndex();
+ // column selection only
+ } else if (cs) {
+ totalCount = table.getSelectedColumnCount();
+ minX = csm.getMinSelectionIndex();
+ maxX = csm.getMaxSelectionIndex();
+ minY = 0;
+ maxY = table.getRowCount() - 1;
+ // no selection allowed
+ } else {
+ totalCount = 0;
+ // A bogus assignment to stop javac from complaining
+ // about unitialized values. In this case, these
+ // won't even be used.
+ minX = maxX = minY = maxY = 0;
+ }
+
+ // For some cases, there is no point in trying to stay within the
+ // selected area. Instead, move outside the selection, wrapping at
+ // the table boundaries. The cases are:
+ boolean stayInSelection;
+
+ // - nothing selected
+ if (totalCount == 0 ||
+ // - one item selected, and the lead is already selected
+ (totalCount == 1 && table.isCellSelected(leadRow, leadColumn))) {
+
+ stayInSelection = false;
+
+ maxX = table.getColumnCount() - 1;
+ maxY = table.getRowCount() - 1;
+
+ // the mins are calculated like this in case the max is -1
+ minX = Math.min(0, maxX);
+ minY = Math.min(0, maxY);
+ } else {
+ stayInSelection = true;
+ }
+
+ // the algorithm below isn't prepared to deal with -1 lead/anchor
+ // so massage appropriately here first
+ if (dy == 1 && leadColumn == -1) {
+ leadColumn = minX;
+ leadRow = -1;
+ } else if (dx == 1 && leadRow == -1) {
+ leadRow = minY;
+ leadColumn = -1;
+ } else if (dy == -1 && leadColumn == -1) {
+ leadColumn = maxX;
+ leadRow = maxY + 1;
+ } else if (dx == -1 && leadRow == -1) {
+ leadRow = maxY;
+ leadColumn = maxX + 1;
+ }
+
+ // In cases where the lead is not within the search range,
+ // we need to bring it within one cell for the search
+ // to work properly. Check these here.
+ leadRow = Math.min(Math.max(leadRow, minY - 1), maxY + 1);
+ leadColumn = Math.min(Math.max(leadColumn, minX - 1), maxX + 1);
+
+ // find the next position, possibly looping until it is selected
+ do {
+ calcNextPos(dx, minX, maxX, dy, minY, maxY);
+ } while (stayInSelection && !table.isCellSelected(leadRow, leadColumn));
+
+ return stayInSelection;
+ }
+
+ protected static int clipToRange(final int i, final int a, final int b) {
+ return Math.min(Math.max(i, a), b - 1);
+ }
+
+ /**
+ * Find the next lead row and column based on the given
+ * dx/dy and max/min values.
+ */
+ protected void calcNextPos(final int dx, final int minX, final int maxX,
+ final int dy, final int minY, final int maxY) {
+
+ if (dx != 0) {
+ leadColumn += dx;
+ if (leadColumn > maxX) {
+ leadColumn = minX;
+ leadRow++;
+ if (leadRow > maxY) {
+ leadRow = minY;
+ }
+ } else if (leadColumn < minX) {
+ leadColumn = maxX;
+ leadRow--;
+ if (leadRow < minY) {
+ leadRow = maxY;
+ }
+ }
+ } else {
+ leadRow += dy;
+ if (leadRow > maxY) {
+ leadRow = minY;
+ leadColumn++;
+ if (leadColumn > maxX) {
+ leadColumn = minX;
+ }
+ } else if (leadRow < minY) {
+ leadRow = maxY;
+ leadColumn--;
+ if (leadColumn < minX) {
+ leadColumn = maxX;
+ }
+ }
+ }
+ }
+
+ @Override
+ public boolean accept(final Object sender) {
+ String key = getName();
+
+ if (sender instanceof JTable &&
+ Boolean.TRUE.equals(((JTable) sender).getClientProperty("Table.isFileList"))) {
+ if (key.equals(NEXT_COLUMN) ||
+ key.equals(NEXT_COLUMN_CELL) ||
+ key.equals(NEXT_COLUMN_EXTEND_SELECTION) ||
+ key.equals(NEXT_COLUMN_CHANGE_LEAD) ||
+ key.equals(PREVIOUS_COLUMN) ||
+ key.equals(PREVIOUS_COLUMN_CELL) ||
+ key.equals(PREVIOUS_COLUMN_EXTEND_SELECTION) ||
+ key.equals(PREVIOUS_COLUMN_CHANGE_LEAD) ||
+ key.equals(SCROLL_LEFT_CHANGE_SELECTION) ||
+ key.equals(SCROLL_LEFT_EXTEND_SELECTION) ||
+ key.equals(SCROLL_RIGHT_CHANGE_SELECTION) ||
+ key.equals(SCROLL_RIGHT_EXTEND_SELECTION) ||
+ key.equals(FIRST_COLUMN) ||
+ key.equals(FIRST_COLUMN_EXTEND_SELECTION) ||
+ key.equals(LAST_COLUMN) ||
+ key.equals(LAST_COLUMN_EXTEND_SELECTION) ||
+ key.equals(NEXT_ROW_CELL) ||
+ key.equals(PREVIOUS_ROW_CELL)) {
+
+ return false;
+ }
+ }
+
+ if (key.equals(CANCEL_EDITING) && sender instanceof JTable) {
+ return ((JTable) sender).isEditing();
+ } else if (key.equals(NEXT_ROW_CHANGE_LEAD) ||
+ key.equals(PREVIOUS_ROW_CHANGE_LEAD)) {
+ // discontinuous selection actions are only enabled for
+ // DefaultListSelectionModel
+ return sender != null &&
+ ((JTable) sender).getSelectionModel()
+ instanceof DefaultListSelectionModel;
+ } else if (key.equals(NEXT_COLUMN_CHANGE_LEAD) ||
+ key.equals(PREVIOUS_COLUMN_CHANGE_LEAD)) {
+ // discontinuous selection actions are only enabled for
+ // DefaultListSelectionModel
+ return sender != null &&
+ ((JTable) sender).getColumnModel().getSelectionModel()
+ instanceof DefaultListSelectionModel;
+ } else if (key.equals(ADD_TO_SELECTION) && sender instanceof JTable) {
+ // This action is typically bound to SPACE.
+ // If the table is already in an editing mode, SPACE should
+ // simply enter a space character into the table, and not
+ // select a cell. Likewise, if the lead cell is already selected
+ // then hitting SPACE should just enter a space character
+ // into the cell and begin editing. In both of these cases
+ // this action will be disabled.
+ JTable table = (JTable) sender;
+ int leadRow = getAdjustedLead(table, true);
+ int leadCol = getAdjustedLead(table, false);
+ return !(table.isEditing() || table.isCellSelected(leadRow, leadCol));
+ } else if (key.equals(FOCUS_HEADER) && sender instanceof JTable) {
+ JTable table = (JTable) sender;
+ return table.getTableHeader() != null;
+ }
+
+ return true;
+ }
+ }
+
+//
+// Factory methods for the Listeners
+//
+
+ @SuppressWarnings("serial") // JDK-implementation class
+ static class TableTransferHandler extends TransferHandler implements UIResource {
+
+ public int getSourceActions(final JComponent c) {
+ return COPY;
+ }
+
+ /**
+ * Create a Transferable to use as the source for a data transfer.
+ *
+ * @param c The component holding the data to be transfered. This
+ * argument is provided to enable sharing of TransferHandlers by
+ * multiple components.
+ * @return The representation of the data to be transfered.
+ */
+ protected Transferable createTransferable(final JComponent c) {
+ if (c instanceof JTable) {
+ JTable table = (JTable) c;
+ int[] rows;
+ int[] cols;
+
+ if (!table.getRowSelectionAllowed() && !table.getColumnSelectionAllowed()) {
+ return null;
+ }
+
+ if (!table.getRowSelectionAllowed()) {
+ int rowCount = table.getRowCount();
+
+ rows = new int[rowCount];
+ for (int counter = 0; counter < rowCount; counter++) {
+ rows[counter] = counter;
+ }
+ } else {
+ rows = table.getSelectedRows();
+ }
+
+ if (!table.getColumnSelectionAllowed()) {
+ int colCount = table.getColumnCount();
+
+ cols = new int[colCount];
+ for (int counter = 0; counter < colCount; counter++) {
+ cols[counter] = counter;
+ }
+ } else {
+ cols = table.getSelectedColumns();
+ }
+
+ if (rows == null || cols == null || rows.length == 0 || cols.length == 0) {
+ return null;
+ }
+
+ StringBuilder plainStr = new StringBuilder();
+ StringBuilder htmlStr = new StringBuilder();
+
+ htmlStr.append("\n\n\n");
+
+ for (int value : rows) {
+ htmlStr.append("\n");
+ for (int i : cols) {
+ Object obj = table.getValueAt(value, i);
+ String val = ((obj == null) ? "" : obj.toString());
+ plainStr.append(val).append('\t');
+ htmlStr.append(" ").append(val).append(" \n");
+ }
+ // we want a newline at the end of each line and not a tab
+ plainStr.deleteCharAt(plainStr.length() - 1).append('\n');
+ htmlStr.append(" \n");
+ }
+
+ // remove the last newline
+ plainStr.deleteCharAt(plainStr.length() - 1);
+ htmlStr.append("
\n\n");
+
+ return new BasicTransferable(plainStr.toString(), htmlStr.toString());
+ }
+
+ return null;
+ }
+
+ }
+
+ protected Handler getHandler() {
+ if (handler == null) {
+ handler = new Handler();
+ }
+ return handler;
+ }
+
+ /**
+ * This class should be treated as a "protected" inner class.
+ * Instantiate it only within subclasses of {@code BasicTableUI}.
+ * As of Java 2 platform v1.3 this class is no longer used.
+ * Instead JTable
+ * overrides processKeyBinding
to dispatch the event to
+ * the current TableCellEditor
.
+ */
+ public class KeyHandler implements KeyListener {
+ public void keyTyped(final KeyEvent e) {
+ getHandler().keyTyped(e);
+ }
+
+ // NOTE: This class exists only for backward compatibility. All
+ // its functionality has been moved into Handler. If you need to add
+ // new functionality add it to the Handler, but make sure this
+ // class calls into the Handler.
+ public void keyPressed(final KeyEvent e) {
+ getHandler().keyPressed(e);
+ }
+
+ public void keyReleased(final KeyEvent e) {
+ getHandler().keyReleased(e);
+ }
+ }
+
+ /**
+ * Creates the key listener for handling keyboard navigation in the {@code JTable}.
+ *
+ * @return the key listener for handling keyboard navigation in the {@code JTable}
+ */
+ protected KeyListener createKeyListener() {
+ return null;
+ }
+
+ /**
+ * This class should be treated as a "protected" inner class.
+ * Instantiate it only within subclasses of {@code BasicTableUI}.
+ */
+ public class FocusHandler implements FocusListener {
+ // NOTE: This class exists only for backward compatibility. All
+ // its functionality has been moved into Handler. If you need to add
+ // new functionality add it to the Handler, but make sure this
+ // class calls into the Handler.
+ public void focusGained(final FocusEvent e) {
+ getHandler().focusGained(e);
+ }
+
+ public void focusLost(final FocusEvent e) {
+ getHandler().focusLost(e);
+ }
+ }
+
+ /**
+ * Creates the focus listener for handling keyboard navigation in the {@code JTable}.
+ *
+ * @return the focus listener for handling keyboard navigation in the {@code JTable}
+ */
+ protected FocusListener createFocusListener() {
+ return getHandler();
+ }
+
+ /**
+ * This class should be treated as a "protected" inner class.
+ * Instantiate it only within subclasses of BasicTableUI.
+ */
+ public class MouseInputHandler implements MouseInputListener {
+ // NOTE: This class exists only for backward compatibility. All
+ // its functionality has been moved into Handler. If you need to add
+ // new functionality add it to the Handler, but make sure this
+ // class calls into the Handler.
+ public void mouseClicked(final MouseEvent e) {
+ getHandler().mouseClicked(e);
+ }
+
+ public void mousePressed(final MouseEvent e) {
+ getHandler().mousePressed(e);
+ }
+
+ public void mouseReleased(final MouseEvent e) {
+ getHandler().mouseReleased(e);
+ }
+
+ public void mouseEntered(final MouseEvent e) {
+ getHandler().mouseEntered(e);
+ }
+
+ public void mouseExited(final MouseEvent e) {
+ getHandler().mouseExited(e);
+ }
+
+ public void mouseDragged(final MouseEvent e) {
+ getHandler().mouseDragged(e);
+ }
+
+ public void mouseMoved(final MouseEvent e) {
+ getHandler().mouseMoved(e);
+ }
+ }
+
+ /**
+ * Creates the mouse listener for the {@code JTable}.
+ *
+ * @return the mouse listener for the {@code JTable}
+ */
+ protected MouseInputListener createMouseInputListener() {
+ return getHandler();
+ }
+
+//
+// The installation/uninstall procedures and support
+//
+
+ protected class Handler implements FocusListener, MouseInputListener,
+ PropertyChangeListener, ListSelectionListener, ActionListener,
+ DragRecognitionSupport.BeforeDrag {
+
+ // Component receiving mouse events during editing.
+ // May not be editorComponent.
+ protected Component dispatchComponent;
+ // The row and column where the press occurred and the
+ // press event itself
+ protected int pressedRow;
+ protected int pressedCol;
+ protected MouseEvent pressedEvent;
+ // Whether or not the mouse press (which is being considered as part
+ // of a drag sequence) also caused the selection change to be fully
+ // processed.
+ protected boolean dragPressDidSelection;
+ // Set to true when a drag gesture has been fully recognized and DnD
+ // begins. Use this to ignore further mouse events which could be
+ // delivered if DnD is cancelled (via ESCAPE for example)
+ protected boolean dragStarted;
+
+
+ // MouseInputListener
+ // Whether or not we should start the editing timer on release
+ protected boolean shouldStartTimer;
+ // To cache the return value of pointOutsidePrefSize since we use
+ // it multiple times.
+ protected boolean outsidePrefSize;
+ // Used to delay the start of editing.
+ protected Timer timer = null;
+
+ public void focusGained(final FocusEvent e) {
+ repaintLeadCell();
+ }
+
+ // FocusListener
+ protected void repaintLeadCell() {
+ int lr = getAdjustedLead(table, true);
+ int lc = getAdjustedLead(table, false);
+
+ if (lr < 0 || lc < 0) {
+ return;
+ }
+
+ Rectangle dirtyRect = table.getCellRect(lr, lc, false);
+ table.repaint(dirtyRect);
+ }
+
+ public void focusLost(final FocusEvent e) {
+ repaintLeadCell();
+ }
+
+ // KeyListener
+ public void keyPressed(final KeyEvent e) {
+ }
+
+ public void keyReleased(final KeyEvent e) {
+ }
+
+ @SuppressWarnings("deprecation")
+ public void keyTyped(final KeyEvent e) {
+ KeyStroke keyStroke = KeyStroke.getKeyStroke(e.getKeyChar(),
+ e.getModifiers());
+
+ // We register all actions using ANCESTOR_OF_FOCUSED_COMPONENT
+ // which means that we might perform the appropriate action
+ // in the table and then forward it to the editor if the editor
+ // had focus. Make sure this doesn't happen by checking our
+ // InputMaps.
+ InputMap map = table.getInputMap(JComponent.WHEN_FOCUSED);
+ if (map != null && map.get(keyStroke) != null) {
+ return;
+ }
+ map = table.getInputMap(JComponent.
+ WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
+ if (map != null && map.get(keyStroke) != null) {
+ return;
+ }
+
+ keyStroke = KeyStroke.getKeyStrokeForEvent(e);
+
+ // The AWT seems to generate an unconsumed \r event when
+ // ENTER (\n) is pressed.
+ if (e.getKeyChar() == '\r') {
+ return;
+ }
+
+ int leadRow = getAdjustedLead(table, true);
+ int leadColumn = getAdjustedLead(table, false);
+ if (leadRow != -1 && leadColumn != -1 && !table.isEditing()) {
+ if (!table.editCellAt(leadRow, leadColumn)) {
+ return;
+ }
+ }
+
+ // Forwarding events this way seems to put the component
+ // in a state where it believes it has focus. In reality
+ // the table retains focus - though it is difficult for
+ // a user to tell, since the caret is visible and flashing.
+
+ // Calling table.requestFocus() here, to get the focus back to
+ // the table, seems to have no effect.
+
+ Component editorComp = table.getEditorComponent();
+ if (table.isEditing() && editorComp != null) {
+ if (editorComp instanceof JComponent) {
+ JComponent component = (JComponent) editorComp;
+ map = component.getInputMap(JComponent.WHEN_FOCUSED);
+ Object binding = (map != null) ? map.get(keyStroke) : null;
+ if (binding == null) {
+ map = component.getInputMap(JComponent.
+ WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
+ binding = (map != null) ? map.get(keyStroke) : null;
+ }
+ if (binding != null) {
+ ActionMap am = component.getActionMap();
+ Action action = (am != null) ? am.get(binding) : null;
+ if (action != null && SwingUtilities.notifyAction(action, keyStroke, e, component,
+ e.getModifiers())) {
+ e.consume();
+ }
+ }
+ }
+ }
+ }
+
+ public void mouseClicked(final MouseEvent e) {
+ }
+
+ public void mousePressed(final MouseEvent e) {
+ if (SwingUtilities2.shouldIgnore(e, table)) {
+ return;
+ }
+
+ if (table.isEditing() && !table.getCellEditor().stopCellEditing()) {
+ Component editorComponent = table.getEditorComponent();
+ if (editorComponent != null && !editorComponent.hasFocus()) {
+ SwingUtilities2.compositeRequestFocus(editorComponent);
+ }
+ return;
+ }
+
+ Point p = e.getPoint();
+ pressedRow = table.rowAtPoint(p);
+ pressedCol = table.columnAtPoint(p);
+ outsidePrefSize = pointOutsidePrefSize(pressedRow, pressedCol, p);
+
+ if (isFileList) {
+ shouldStartTimer =
+ table.isCellSelected(pressedRow, pressedCol) &&
+ !e.isShiftDown() &&
+ !DarkUIUtil.isMenuShortcutKeyDown(e) &&
+ !outsidePrefSize;
+ }
+
+ if (table.getDragEnabled()) {
+ mousePressedDND(e);
+ } else {
+ SwingUtilities2.adjustFocus(table);
+ if (!isFileList) {
+ setValueIsAdjusting(true);
+ }
+ adjustSelection(e);
+ }
+ }
+
+ public void mouseReleased(final MouseEvent e) {
+ if (SwingUtilities2.shouldIgnore(e, table)) {
+ return;
+ }
+
+ if (table.getDragEnabled()) {
+ mouseReleasedDND(e);
+ } else {
+ if (isFileList) {
+ maybeStartTimer();
+ }
+ }
+
+ pressedEvent = null;
+ repostEvent(e);
+ dispatchComponent = null;
+ setValueIsAdjusting(false);
+ }
+
+ protected void mouseReleasedDND(final MouseEvent e) {
+ MouseEvent me = DragRecognitionSupport.mouseReleased(e);
+ if (me != null) {
+ SwingUtilities2.adjustFocus(table);
+ if (!dragPressDidSelection) {
+ adjustSelection(me);
+ }
+ }
+
+ if (!dragStarted) {
+ if (isFileList) {
+ maybeStartTimer();
+ return;
+ }
+
+ Point p = e.getPoint();
+
+ if (pressedEvent != null &&
+ table.rowAtPoint(p) == pressedRow &&
+ table.columnAtPoint(p) == pressedCol &&
+ table.editCellAt(pressedRow, pressedCol, pressedEvent)) {
+
+ setDispatchComponent(pressedEvent);
+ repostEvent(pressedEvent);
+
+ // This may appear completely odd, but must be done for backward
+ // compatibility reasons. Developers have been known to rely on
+ // a call to shouldSelectCell after editing has begun.
+ CellEditor ce = table.getCellEditor();
+ if (ce != null) {
+ ce.shouldSelectCell(pressedEvent);
+ }
+ }
+ }
+ }
+
+ protected void maybeStartTimer() {
+ if (!shouldStartTimer) {
+ return;
+ }
+
+ if (timer == null) {
+ int delay = UIManager.getInt("FileChooser.editDelay");
+ if (delay == 0) delay = 1200;
+ timer = new Timer(delay, this);
+ timer.setRepeats(false);
+ }
+
+ timer.start();
+ }
+
+ protected boolean repostEvent(final MouseEvent e) {
+ // Check for isEditing() in case another event has
+ // caused the editor to be removed. See bug #4306499.
+ if (dispatchComponent == null || !table.isEditing()) {
+ return false;
+ }
+ MouseEvent e2 = SwingUtilities.convertMouseEvent(table, e,
+ dispatchComponent);
+ dispatchComponent.dispatchEvent(e2);
+ return true;
+ }
+
+ protected void setValueIsAdjusting(final boolean flag) {
+ table.getSelectionModel().setValueIsAdjusting(flag);
+ table.getColumnModel().getSelectionModel().
+ setValueIsAdjusting(flag);
+ }
+
+ protected void adjustSelection(final MouseEvent e) {
+ // Fix for 4835633
+ if (outsidePrefSize) {
+ // If shift is down in multi-select, we should just return.
+ // For single select or non-shift-click, clear the selection
+ if (e.getID() == MouseEvent.MOUSE_PRESSED &&
+ (!e.isShiftDown() ||
+ table.getSelectionModel().getSelectionMode() ==
+ ListSelectionModel.SINGLE_SELECTION)) {
+ table.clearSelection();
+ TableCellEditor tce = table.getCellEditor();
+ if (tce != null) {
+ tce.stopCellEditing();
+ }
+ }
+ return;
+ }
+ // The autoscroller can generate drag events outside the
+ // table's range.
+ if ((pressedCol == -1) || (pressedRow == -1)) {
+ return;
+ }
+
+ boolean dragEnabled = table.getDragEnabled();
+
+ if (!dragEnabled && !isFileList && table.editCellAt(pressedRow, pressedCol, e)) {
+ setDispatchComponent(e);
+ repostEvent(e);
+ }
+
+ CellEditor editor = table.getCellEditor();
+ if (dragEnabled || editor == null || editor.shouldSelectCell(e)) {
+ table.changeSelection(pressedRow, pressedCol,
+ DarkUIUtil.isMenuShortcutKeyDown(e),
+ e.isShiftDown());
+ }
+ }
+
+ protected void setDispatchComponent(final MouseEvent e) {
+ Component editorComponent = table.getEditorComponent();
+ Point p = e.getPoint();
+ Point p2 = SwingUtilities.convertPoint(table, p, editorComponent);
+ dispatchComponent =
+ SwingUtilities.getDeepestComponentAt(editorComponent,
+ p2.x, p2.y);
+ SwingUtilities2.setSkipClickCount(dispatchComponent,
+ e.getClickCount() - 1);
+ }
+
+ public void mouseEntered(final MouseEvent e) {
+ }
+
+ public void mouseExited(final MouseEvent e) {
+ }
+
+ protected boolean canStartDrag() {
+ if (pressedRow == -1 || pressedCol == -1) {
+ return false;
+ }
+
+ if (isFileList) {
+ return !outsidePrefSize;
+ }
+
+ // if this is a single selection table
+ if ((table.getSelectionModel().getSelectionMode() ==
+ ListSelectionModel.SINGLE_SELECTION) &&
+ (table.getColumnModel().getSelectionModel().getSelectionMode() ==
+ ListSelectionModel.SINGLE_SELECTION)) {
+
+ return true;
+ }
+
+ return table.isCellSelected(pressedRow, pressedCol);
+ }
+
+ protected void mousePressedDND(final MouseEvent e) {
+ pressedEvent = e;
+ boolean grabFocus = true;
+ dragStarted = false;
+
+ if (canStartDrag() && DragRecognitionSupport.mousePressed(e)) {
+
+ dragPressDidSelection = false;
+
+ if (DarkUIUtil.isMenuShortcutKeyDown(e) && isFileList) {
+ // do nothing for control - will be handled on release
+ // or when drag starts
+ return;
+ } else if (!e.isShiftDown() && table.isCellSelected(pressedRow, pressedCol)) {
+ // clicking on something that's already selected
+ // and need to make it the lead now
+ table.getSelectionModel().addSelectionInterval(pressedRow,
+ pressedRow);
+ table.getColumnModel().getSelectionModel().
+ addSelectionInterval(pressedCol, pressedCol);
+
+ return;
+ }
+
+ dragPressDidSelection = true;
+
+ // could be a drag initiating event - don't grab focus
+ grabFocus = false;
+ } else if (!isFileList) {
+ // When drag can't happen, mouse drags might change the selection in the table
+ // so we want the isAdjusting flag to be set
+ setValueIsAdjusting(true);
+ }
+
+ if (grabFocus) {
+ SwingUtilities2.adjustFocus(table);
+ }
+
+ adjustSelection(e);
+ }
+
+ public void valueChanged(final ListSelectionEvent e) {
+ if (timer != null) {
+ timer.stop();
+ timer = null;
+ }
+ }
+
+ public void actionPerformed(final ActionEvent ae) {
+ table.editCellAt(pressedRow, pressedCol, null);
+ Component editorComponent = table.getEditorComponent();
+ if (editorComponent != null && !editorComponent.hasFocus()) {
+ SwingUtilities2.compositeRequestFocus(editorComponent);
+ }
+ }
+
+ public void dragStarting(final MouseEvent me) {
+ dragStarted = true;
+
+ if (DarkUIUtil.isMenuShortcutKeyDown(me) && isFileList) {
+ table.getSelectionModel().addSelectionInterval(pressedRow, pressedRow);
+ table.getColumnModel().getSelectionModel().
+ addSelectionInterval(pressedCol, pressedCol);
+ }
+
+ pressedEvent = null;
+ }
+
+ public void mouseDragged(final MouseEvent e) {
+ if (SwingUtilities2.shouldIgnore(e, table)) {
+ return;
+ }
+
+ if (table.getDragEnabled() &&
+ (DragRecognitionSupport.mouseDragged(e, this) || dragStarted)) {
+
+ return;
+ }
+
+ repostEvent(e);
+
+ // Check isFileList:
+ // Until we support drag-selection, dragging should not change
+ // the selection (act like single-select).
+ if (isFileList || table.isEditing()) {
+ return;
+ }
+
+ Point p = e.getPoint();
+ int row = table.rowAtPoint(p);
+ int column = table.columnAtPoint(p);
+ // The autoscroller can generate drag events outside the
+ // table's range.
+ if ((column == -1) || (row == -1)) {
+ return;
+ }
+
+ table.changeSelection(row, column, DarkUIUtil.isMenuShortcutKeyDown(e), true);
+ }
+
+ public void mouseMoved(final MouseEvent e) {
+ }
+
+ // PropertyChangeListener
+ public void propertyChange(final PropertyChangeEvent event) {
+ String changeName = event.getPropertyName();
+
+ if ("componentOrientation".equals(changeName)) {
+ InputMap inputMap = getInputMap(
+ JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
+
+ SwingUtilities.replaceUIInputMap(table,
+ JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT,
+ inputMap);
+
+ JTableHeader header = table.getTableHeader();
+ if (header != null) {
+ header.setComponentOrientation(
+ (ComponentOrientation) event.getNewValue());
+ }
+ } else if ("dropLocation".equals(changeName)) {
+ JTable.DropLocation oldValue = (JTable.DropLocation) event.getOldValue();
+ repaintDropLocation(oldValue);
+ repaintDropLocation(table.getDropLocation());
+ } else if ("Table.isFileList".equals(changeName)) {
+ isFileList = Boolean.TRUE.equals(table.getClientProperty("Table.isFileList"));
+ table.revalidate();
+ table.repaint();
+ if (isFileList) {
+ table.getSelectionModel().addListSelectionListener(getHandler());
+ } else {
+ table.getSelectionModel().removeListSelectionListener(getHandler());
+ timer = null;
+ }
+ } else if ("selectionModel".equals(changeName)) {
+ if (isFileList) {
+ ListSelectionModel old = (ListSelectionModel) event.getOldValue();
+ old.removeListSelectionListener(getHandler());
+ table.getSelectionModel().addListSelectionListener(getHandler());
+ }
+ }
+ }
+
+ protected void repaintDropLocation(final JTable.DropLocation loc) {
+ if (loc == null) {
+ return;
+ }
+
+ if (!loc.isInsertRow() && !loc.isInsertColumn()) {
+ Rectangle rect = table.getCellRect(loc.getRow(), loc.getColumn(), false);
+ if (rect != null) {
+ table.repaint(rect);
+ }
+ return;
+ }
+
+ if (loc.isInsertRow()) {
+ Rectangle rect = extendRect(getHDropLineRect(loc), true);
+ if (rect != null) {
+ table.repaint(rect);
+ }
+ }
+
+ if (loc.isInsertColumn()) {
+ Rectangle rect = extendRect(getVDropLineRect(loc), false);
+ if (rect != null) {
+ table.repaint(rect);
+ }
+ }
+ }
+ }
+
+// Installation
+
+ public void installUI(final JComponent c) {
+ table = (JTable) c;
+
+ rendererPane = new CellRendererPane();
+ table.add(rendererPane);
+ installDefaults();
+ installDefaults2();
+ installListeners();
+ installKeyboardActions();
+ }
+
+ /**
+ * Initialize JTable properties, e.g. font, foreground, and background.
+ * The font, foreground, and background properties are only set if their
+ * current value is either null or a UIResource, other properties are set
+ * if the current value is null.
+ *
+ * @see #installUI
+ */
+ protected void installDefaults() {
+ LookAndFeel.installColorsAndFont(table, "Table.background",
+ "Table.foreground", "Table.font");
+ // JTable's original row height is 16. To correctly display the
+ // contents on Linux we should have set it to 18, Windows 19 and
+ // Solaris 20. As these values vary so much it's too hard to
+ // be backward compatable and try to update the row height, we're
+ // therefor NOT going to adjust the row height based on font. If the
+ // developer changes the font, it's there responsability to update
+ // the row height.
+
+ LookAndFeel.installProperty(table, "opaque", Boolean.TRUE);
+
+ Color sbg = table.getSelectionBackground();
+ if (sbg == null || sbg instanceof UIResource) {
+ sbg = UIManager.getColor("Table.selectionBackground");
+ table.setSelectionBackground(sbg != null ? sbg : UIManager.getColor("textHighlight"));
+ }
+
+ Color sfg = table.getSelectionForeground();
+ if (sfg == null || sfg instanceof UIResource) {
+ sfg = UIManager.getColor("Table.selectionForeground");
+ table.setSelectionForeground(sfg != null ? sfg : UIManager.getColor("textHighlightText"));
+ }
+
+ Color gridColor = table.getGridColor();
+ if (gridColor == null || gridColor instanceof UIResource) {
+ gridColor = UIManager.getColor("Table.gridColor");
+ table.setGridColor(gridColor != null ? gridColor : Color.GRAY);
+ }
+
+ // install the scrollpane border
+ Container parent = SwingUtilities.getUnwrappedParent(table); // should be viewport
+ if (parent != null) {
+ parent = parent.getParent(); // should be the scrollpane
+ if (parent instanceof JScrollPane) {
+ LookAndFeel.installBorder((JScrollPane) parent, "Table.scrollPaneBorder");
+ }
+ }
+
+ isFileList = Boolean.TRUE.equals(table.getClientProperty("Table.isFileList"));
+ }
+
+ protected void installDefaults2() {
+ TransferHandler th = table.getTransferHandler();
+ if (th == null || th instanceof UIResource) {
+ table.setTransferHandler(defaultTransferHandler);
+ // default TransferHandler doesn't support drop
+ // so we don't want drop handling
+ if (table.getDropTarget() instanceof UIResource) {
+ table.setDropTarget(null);
+ }
+ }
+ }
+
+ /**
+ * Attaches listeners to the JTable.
+ */
+ protected void installListeners() {
+ focusListener = createFocusListener();
+ keyListener = createKeyListener();
+ mouseInputListener = createMouseInputListener();
+
+ table.addFocusListener(focusListener);
+ table.addKeyListener(keyListener);
+ table.addMouseListener(mouseInputListener);
+ table.addMouseMotionListener(mouseInputListener);
+ table.addPropertyChangeListener(getHandler());
+ if (isFileList) {
+ table.getSelectionModel().addListSelectionListener(getHandler());
+ }
+ }
+
+ /**
+ * Register all keyboard actions on the JTable.
+ */
+ protected void installKeyboardActions() {
+ LazyActionMap.installLazyActionMap(table, BasicTableUI.class, "Table.actionMap");
+
+ InputMap inputMap = getInputMap(JComponent.
+ WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
+ SwingUtilities.replaceUIInputMap(table,
+ JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT,
+ inputMap);
+ }
+
+ InputMap getInputMap(final int condition) {
+ if (condition == JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT) {
+ InputMap keyMap =
+ (InputMap) DefaultLookup.get(table, this,
+ "Table.ancestorInputMap");
+ InputMap rtlKeyMap;
+
+ if (table.getComponentOrientation().isLeftToRight() ||
+ ((rtlKeyMap = (InputMap) DefaultLookup.get(table, this,
+ "Table.ancestorInputMap.RightToLeft")) == null)) {
+ return keyMap;
+ } else {
+ rtlKeyMap.setParent(keyMap);
+ return rtlKeyMap;
+ }
+ }
+ return null;
+ }
+
+ // Uninstallation
+ public void uninstallUI(final JComponent c) {
+ uninstallDefaults();
+ uninstallListeners();
+ uninstallKeyboardActions();
+
+ table.remove(rendererPane);
+ rendererPane = null;
+ table = null;
+ }
+
+ /**
+ * Uninstalls default properties.
+ */
+ protected void uninstallDefaults() {
+ if (table.getTransferHandler() instanceof UIResource) {
+ table.setTransferHandler(null);
+ }
+ }
+
+ /**
+ * Unregisters listeners.
+ */
+ protected void uninstallListeners() {
+ table.removeFocusListener(focusListener);
+ table.removeKeyListener(keyListener);
+ table.removeMouseListener(mouseInputListener);
+ table.removeMouseMotionListener(mouseInputListener);
+ table.removePropertyChangeListener(getHandler());
+ if (isFileList) {
+ table.getSelectionModel().removeListSelectionListener(getHandler());
+ }
+
+ focusListener = null;
+ keyListener = null;
+ mouseInputListener = null;
+ handler = null;
+ }
+
+ /**
+ * Unregisters keyboard actions.
+ */
+ protected void uninstallKeyboardActions() {
+ SwingUtilities.replaceUIInputMap(table, JComponent.
+ WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, null);
+ SwingUtilities.replaceUIActionMap(table, null);
+ }
+
+ /**
+ * Returns the baseline.
+ *
+ * @throws NullPointerException {@inheritDoc}
+ * @throws IllegalArgumentException {@inheritDoc}
+ * @see javax.swing.JComponent#getBaseline(int, int)
+ * @since 1.6
+ */
+ public int getBaseline(final JComponent c, final int width, final int height) {
+ super.getBaseline(c, width, height);
+ UIDefaults lafDefaults = UIManager.getLookAndFeelDefaults();
+ Component renderer = (Component) lafDefaults.get(
+ BASELINE_COMPONENT_KEY);
+ if (renderer == null) {
+ DefaultTableCellRenderer tcr = new DefaultTableCellRenderer();
+ renderer = tcr.getTableCellRendererComponent(
+ table, "a", false, false, -1, -1);
+ lafDefaults.put(BASELINE_COMPONENT_KEY, renderer);
+ }
+ renderer.setFont(table.getFont());
+ int rowMargin = table.getRowMargin();
+ return renderer.getBaseline(Integer.MAX_VALUE, table.getRowHeight() -
+ rowMargin) + rowMargin / 2;
+ }
+
+ /**
+ * Returns an enum indicating how the baseline of the component
+ * changes as the size changes.
+ *
+ * @throws NullPointerException {@inheritDoc}
+ * @see javax.swing.JComponent#getBaseline(int, int)
+ * @since 1.6
+ */
+ public Component.BaselineResizeBehavior getBaselineResizeBehavior(
+ final JComponent c) {
+ super.getBaselineResizeBehavior(c);
+ return Component.BaselineResizeBehavior.CONSTANT_ASCENT;
+ }
+
+//
+// Size Methods
+//
+
+ protected Dimension createTableSize(final long width) {
+ int height = 0;
+ int rowCount = table.getRowCount();
+ if (rowCount > 0 && table.getColumnCount() > 0) {
+ Rectangle r = table.getCellRect(rowCount - 1, 0, true);
+ height = r.y + r.height;
+ }
+ // Width is always positive. The call to abs() is a workaround for
+ // a bug in the 1.1.6 JIT on Windows.
+ long tmp = Math.abs(width);
+ if (tmp > Integer.MAX_VALUE) {
+ tmp = Integer.MAX_VALUE;
+ }
+ return new Dimension((int) tmp, height);
+ }
+
+ /**
+ * Return the minimum size of the table. The minimum height is the
+ * row height times the number of rows.
+ * The minimum width is the sum of the minimum widths of each column.
+ */
+ public Dimension getMinimumSize(final JComponent c) {
+ long width = 0;
+ Enumeration enumeration = table.getColumnModel().getColumns();
+ while (enumeration.hasMoreElements()) {
+ TableColumn aColumn = enumeration.nextElement();
+ width = width + aColumn.getMinWidth();
+ }
+ return createTableSize(width);
+ }
+
+ /**
+ * Return the preferred size of the table. The preferred height is the
+ * row height times the number of rows.
+ * The preferred width is the sum of the preferred widths of each column.
+ */
+ public Dimension getPreferredSize(final JComponent c) {
+ long width = 0;
+ Enumeration enumeration = table.getColumnModel().getColumns();
+ while (enumeration.hasMoreElements()) {
+ TableColumn aColumn = enumeration.nextElement();
+ width = width + aColumn.getPreferredWidth();
+ }
+ return createTableSize(width);
+ }
+
+ /**
+ * Return the maximum size of the table. The maximum height is the
+ * row heighttimes the number of rows.
+ * The maximum width is the sum of the maximum widths of each column.
+ */
+ public Dimension getMaximumSize(final JComponent c) {
+ long width = 0;
+ Enumeration enumeration = table.getColumnModel().getColumns();
+ while (enumeration.hasMoreElements()) {
+ TableColumn aColumn = enumeration.nextElement();
+ width = width + aColumn.getMaxWidth();
+ }
+ return createTableSize(width);
+ }
+
+//
+// Paint methods and support
+//
+
+ /**
+ * Paint a representation of the table
instance
+ * that was set in installUI().
+ */
+ public void paint(final Graphics g, final JComponent c) {
+ Rectangle clip = g.getClipBounds();
+
+ Rectangle bounds = table.getBounds();
+ // account for the fact that the graphics has already been translated
+ // into the table's bounds
+ bounds.x = bounds.y = 0;
+
+ if (table.getRowCount() <= 0 || table.getColumnCount() <= 0 ||
+ // this check prevents us from painting the entire table
+ // when the clip doesn't intersect our bounds at all
+ !bounds.intersects(clip)) {
+
+ paintDropLines(g);
+ return;
+ }
+
+ boolean ltr = table.getComponentOrientation().isLeftToRight();
+ Point upperLeft, lowerRight;
+ // compute the visible part of table which needs to be painted
+ Rectangle visibleBounds = clip.intersection(bounds);
+ upperLeft = visibleBounds.getLocation();
+ lowerRight = new Point(visibleBounds.x + visibleBounds.width - 1,
+ visibleBounds.y + visibleBounds.height - 1);
+
+ int rMin = table.rowAtPoint(upperLeft);
+ int rMax = table.rowAtPoint(lowerRight);
+ // This should never happen (as long as our bounds intersect the clip,
+ // which is why we bail above if that is the case).
+ if (rMin == -1) {
+ rMin = 0;
+ }
+ // If the table does not have enough rows to fill the view we'll get -1.
+ // (We could also get -1 if our bounds don't intersect the clip,
+ // which is why we bail above if that is the case).
+ // Replace this with the index of the last row.
+ if (rMax == -1) {
+ rMax = table.getRowCount() - 1;
+ }
+
+ // For FIT_WIDTH, all columns should be printed irrespective of
+ // how many columns are visible. So, we used clip which is already set to
+ // total col width instead of visible region
+ // Since JTable.PrintMode is not accessible
+ // from here, we aet "Table.printMode" in TablePrintable#print and
+ // access from here.
+ Object printMode = table.getClientProperty("Table.printMode");
+ if ((printMode == JTable.PrintMode.FIT_WIDTH)) {
+ upperLeft = clip.getLocation();
+ lowerRight = new Point(clip.x + clip.width - 1,
+ clip.y + clip.height - 1);
+ }
+ int cMin = table.columnAtPoint(ltr ? upperLeft : lowerRight);
+ int cMax = table.columnAtPoint(ltr ? lowerRight : upperLeft);
+ // This should never happen.
+ if (cMin == -1) {
+ cMin = 0;
+ }
+ // If the table does not have enough columns to fill the view we'll get -1.
+ // Replace this with the index of the last column.
+ if (cMax == -1) {
+ cMax = table.getColumnCount() - 1;
+ }
+
+ Container comp = SwingUtilities.getUnwrappedParent(table);
+ if (comp != null) {
+ comp = comp.getParent();
+ }
+
+ if (comp != null && !(comp instanceof JViewport) && !(comp instanceof JScrollPane)) {
+ // We did rMax-1 to paint the same number of rows that are drawn on console
+ // otherwise 1 extra row is printed per page than that are displayed
+ // when there is no scrollPane and we do printing of table
+ // but not when rmax is already pointing to index of last row
+ // and if there is any selected rows
+ if (rMax != (table.getRowCount() - 1) &&
+ (table.getSelectedRow() == -1)) {
+ // Do not decrement rMax if rMax becomes
+ // less than or equal to rMin
+ // else cells will not be painted
+ if (rMax - rMin > 1) {
+ rMax = rMax - 1;
+ }
+ }
+ }
+
+ // Paint the grid.
+ paintGrid(g, rMin, rMax, cMin, cMax);
+
+ // Paint the cells.
+ paintCells(g, rMin, rMax, cMin, cMax);
+
+ paintDropLines(g);
+ }
+
+ protected void paintDropLines(final Graphics g) {
+ JTable.DropLocation loc = table.getDropLocation();
+ if (loc == null) {
+ return;
+ }
+
+ Color color = UIManager.getColor("Table.dropLineColor");
+ Color shortColor = UIManager.getColor("Table.dropLineShortColor");
+ if (color == null && shortColor == null) {
+ return;
+ }
+
+ Rectangle rect;
+
+ rect = getHDropLineRect(loc);
+ if (rect != null) {
+ int x = rect.x;
+ int w = rect.width;
+ if (color != null) {
+ extendRect(rect, true);
+ g.setColor(color);
+ g.fillRect(rect.x, rect.y, rect.width, rect.height);
+ }
+ if (!loc.isInsertColumn() && shortColor != null) {
+ g.setColor(shortColor);
+ g.fillRect(x, rect.y, w, rect.height);
+ }
+ }
+
+ rect = getVDropLineRect(loc);
+ if (rect != null) {
+ int y = rect.y;
+ int h = rect.height;
+ if (color != null) {
+ extendRect(rect, false);
+ g.setColor(color);
+ g.fillRect(rect.x, rect.y, rect.width, rect.height);
+ }
+ if (!loc.isInsertRow() && shortColor != null) {
+ g.setColor(shortColor);
+ g.fillRect(rect.x, y, rect.width, h);
+ }
+ }
+ }
+
+ protected Rectangle getHDropLineRect(final JTable.DropLocation loc) {
+ if (!loc.isInsertRow()) {
+ return null;
+ }
+
+ int row = loc.getRow();
+ int col = loc.getColumn();
+ if (col >= table.getColumnCount()) {
+ col--;
+ }
+
+ Rectangle rect = table.getCellRect(row, col, true);
+
+ if (row >= table.getRowCount()) {
+ row--;
+ Rectangle prevRect = table.getCellRect(row, col, true);
+ rect.y = prevRect.y + prevRect.height;
+ }
+
+ if (rect.y == 0) {
+ rect.y = -1;
+ } else {
+ rect.y -= 2;
+ }
+
+ rect.height = 3;
+
+ return rect;
+ }
+
+ protected Rectangle getVDropLineRect(final JTable.DropLocation loc) {
+ if (!loc.isInsertColumn()) {
+ return null;
+ }
+
+ boolean ltr = table.getComponentOrientation().isLeftToRight();
+ int col = loc.getColumn();
+ Rectangle rect = table.getCellRect(loc.getRow(), col, true);
+
+ if (col >= table.getColumnCount()) {
+ col--;
+ rect = table.getCellRect(loc.getRow(), col, true);
+ if (ltr) {
+ rect.x = rect.x + rect.width;
+ }
+ } else if (!ltr) {
+ rect.x = rect.x + rect.width;
+ }
+
+ if (rect.x == 0) {
+ rect.x = -1;
+ } else {
+ rect.x -= 2;
+ }
+
+ rect.width = 3;
+
+ return rect;
+ }
+
+ protected Rectangle extendRect(final Rectangle rect, final boolean horizontal) {
+ if (rect == null) {
+ return rect;
+ }
+
+ if (horizontal) {
+ rect.x = 0;
+ rect.width = table.getWidth();
+ } else {
+ rect.y = 0;
+
+ if (table.getRowCount() != 0) {
+ Rectangle lastRect = table.getCellRect(table.getRowCount() - 1, 0, true);
+ rect.height = lastRect.y + lastRect.height;
+ } else {
+ rect.height = table.getHeight();
+ }
+ }
+
+ return rect;
+ }
+
+ /*
+ * Paints the grid lines within aRect , using the grid
+ * color set with setGridColor . Paints vertical lines
+ * if getShowVerticalLines()
returns true and paints
+ * horizontal lines if getShowHorizontalLines()
+ * returns true.
+ */
+ protected void paintGrid(final Graphics g, final int rMin, final int rMax, final int cMin, final int cMax) {
+ g.setColor(table.getGridColor());
+
+ Rectangle minCell = table.getCellRect(rMin, cMin, true);
+ Rectangle maxCell = table.getCellRect(rMax, cMax, true);
+ Rectangle damagedArea = minCell.union(maxCell);
+
+ if (table.getShowHorizontalLines()) {
+ int tableWidth = damagedArea.x + damagedArea.width;
+ int y = damagedArea.y;
+ for (int row = rMin; row <= rMax; row++) {
+ y += table.getRowHeight(row);
+ SwingUtilities2.drawHLine(g, damagedArea.x, tableWidth - 1, y - 1);
+ }
+ }
+ if (table.getShowVerticalLines()) {
+ TableColumnModel cm = table.getColumnModel();
+ int tableHeight = damagedArea.y + damagedArea.height;
+ int x;
+ if (table.getComponentOrientation().isLeftToRight()) {
+ x = damagedArea.x;
+ for (int column = cMin; column <= cMax; column++) {
+ int w = cm.getColumn(column).getWidth();
+ x += w;
+ SwingUtilities2.drawVLine(g, x - 1, 0, tableHeight - 1);
+ }
+ } else {
+ x = damagedArea.x;
+ for (int column = cMax; column >= cMin; column--) {
+ int w = cm.getColumn(column).getWidth();
+ x += w;
+ SwingUtilities2.drawVLine(g, x - 1, 0, tableHeight - 1);
+ }
+ }
+ }
+ }
+
+ protected int viewIndexForColumn(final TableColumn aColumn) {
+ TableColumnModel cm = table.getColumnModel();
+ for (int column = 0; column < cm.getColumnCount(); column++) {
+ if (cm.getColumn(column) == aColumn) {
+ return column;
+ }
+ }
+ return -1;
+ }
+
+ protected void paintCells(final Graphics g, final int rMin, final int rMax, final int cMin, final int cMax) {
+ JTableHeader header = table.getTableHeader();
+ TableColumn draggedColumn = (header == null) ? null : header.getDraggedColumn();
+
+ TableColumnModel cm = table.getColumnModel();
+ int columnMargin = cm.getColumnMargin();
+
+ Rectangle cellRect;
+ TableColumn aColumn;
+ int columnWidth;
+ if (table.getComponentOrientation().isLeftToRight()) {
+ for (int row = rMin; row <= rMax; row++) {
+ cellRect = table.getCellRect(row, cMin, false);
+ for (int column = cMin; column <= cMax; column++) {
+ aColumn = cm.getColumn(column);
+ columnWidth = aColumn.getWidth();
+ cellRect.width = columnWidth - columnMargin;
+ if (aColumn != draggedColumn) {
+ paintCell(g, cellRect, row, column);
+ }
+ cellRect.x += columnWidth;
+ }
+ }
+ } else {
+ for (int row = rMin; row <= rMax; row++) {
+ cellRect = table.getCellRect(row, cMin, false);
+ aColumn = cm.getColumn(cMin);
+ if (aColumn != draggedColumn) {
+ columnWidth = aColumn.getWidth();
+ cellRect.width = columnWidth - columnMargin;
+ paintCell(g, cellRect, row, cMin);
+ }
+ for (int column = cMin + 1; column <= cMax; column++) {
+ aColumn = cm.getColumn(column);
+ columnWidth = aColumn.getWidth();
+ cellRect.width = columnWidth - columnMargin;
+ cellRect.x -= columnWidth;
+ if (aColumn != draggedColumn) {
+ paintCell(g, cellRect, row, column);
+ }
+ }
+ }
+ }
+
+ // Paint the dragged column if we are dragging.
+ if (draggedColumn != null) {
+ paintDraggedArea(g, rMin, rMax, draggedColumn, header.getDraggedDistance());
+ }
+
+ // Remove any renderers that may be left in the rendererPane.
+ rendererPane.removeAll();
+ }
+
+ protected void paintDraggedArea(final Graphics g, final int rMin, final int rMax, final TableColumn draggedColumn, final int distance) {
+ int draggedColumnIndex = viewIndexForColumn(draggedColumn);
+
+ Rectangle minCell = table.getCellRect(rMin, draggedColumnIndex, true);
+ Rectangle maxCell = table.getCellRect(rMax, draggedColumnIndex, true);
+
+ Rectangle vacatedColumnRect = minCell.union(maxCell);
+
+ // Paint a gray well in place of the moving column.
+ g.setColor(table.getParent().getBackground());
+ g.fillRect(vacatedColumnRect.x, vacatedColumnRect.y,
+ vacatedColumnRect.width, vacatedColumnRect.height);
+
+ // Move to the where the cell has been dragged.
+ vacatedColumnRect.x += distance;
+
+ // Fill the background.
+ g.setColor(table.getBackground());
+ g.fillRect(vacatedColumnRect.x, vacatedColumnRect.y,
+ vacatedColumnRect.width, vacatedColumnRect.height);
+
+ // Paint the vertical grid lines if necessary.
+ if (table.getShowVerticalLines()) {
+ g.setColor(table.getGridColor());
+ int x1 = vacatedColumnRect.x;
+ int y1 = vacatedColumnRect.y;
+ int x2 = x1 + vacatedColumnRect.width - 1;
+ int y2 = y1 + vacatedColumnRect.height - 1;
+ // Left
+ g.drawLine(x1 - 1, y1, x1 - 1, y2);
+ // Right
+ g.drawLine(x2, y1, x2, y2);
+ }
+
+ for (int row = rMin; row <= rMax; row++) {
+ // Render the cell value
+ Rectangle r = table.getCellRect(row, draggedColumnIndex, false);
+ r.x += distance;
+ paintCell(g, r, row, draggedColumnIndex);
+
+ // Paint the (lower) horizontal grid line if necessary.
+ if (table.getShowHorizontalLines()) {
+ g.setColor(table.getGridColor());
+ Rectangle rcr = table.getCellRect(row, draggedColumnIndex, true);
+ rcr.x += distance;
+ int x1 = rcr.x;
+ int y1 = rcr.y;
+ int x2 = x1 + rcr.width - 1;
+ int y2 = y1 + rcr.height - 1;
+ g.drawLine(x1, y2, x2, y2);
+ }
+ }
+ }
+
+ protected void paintCell(final Graphics g, final Rectangle cellRect, final int row, final int column) {
+ if (table.isEditing() && table.getEditingRow() == row &&
+ table.getEditingColumn() == column) {
+ Component component = table.getEditorComponent();
+ component.setBounds(cellRect);
+ component.validate();
+ } else {
+ TableCellRenderer renderer = table.getCellRenderer(row, column);
+ Component component = table.prepareRenderer(renderer, row, column);
+ rendererPane.paintComponent(g, component, table, cellRect.x, cellRect.y,
+ cellRect.width, cellRect.height, true);
+ }
+ }
+
+
+}
diff --git a/src/main/java/com/weis/darklaf/ui/table/DarkTableCellEditor.java b/src/main/java/com/weis/darklaf/ui/table/DarkTableCellEditor.java
index 2618c96f..b53eaebb 100644
--- a/src/main/java/com/weis/darklaf/ui/table/DarkTableCellEditor.java
+++ b/src/main/java/com/weis/darklaf/ui/table/DarkTableCellEditor.java
@@ -23,6 +23,7 @@ import java.util.EventObject;
public class DarkTableCellEditor extends DefaultCellEditor {
private static final JCheckBox dummyCheckBox = new JCheckBox();
+ private static final IconWrapper iconWrapper = new IconWrapper();
private final DarkTableCellEditorToggleButton checkBoxEditor =
new DarkTableCellEditorToggleButton(this, new DarkTableCellEditorToggleButton.CellCheckBox());
@@ -39,6 +40,7 @@ public class DarkTableCellEditor extends DefaultCellEditor {
public DarkTableCellEditor(final JTextField textField) {
super(textField);
textField.setBorder(new TextFieldTableCellEditorBorder());
+ textField.putClientProperty("JTextField.isCellEditor", Boolean.TRUE);
setClickCountToStart(2);
}
@@ -148,7 +150,8 @@ public class DarkTableCellEditor extends DefaultCellEditor {
}
@Override
- public boolean isCellEditable(@NotNull final EventObject anEvent) {
+ public boolean isCellEditable(final EventObject anEvent) {
+ if (anEvent == null) return super.isCellEditable(anEvent);
var table = ((JTable) anEvent.getSource());
if (DarkTableCellRenderer.isBooleanRenderingEnabled(table) && anEvent instanceof MouseEvent) {
var p = ((MouseEvent) anEvent).getPoint();
@@ -191,6 +194,8 @@ public class DarkTableCellEditor extends DefaultCellEditor {
delegate.setValue(value);
+ var comp = editorComponent;
+
if (editorComponent instanceof JComboBox) {
((JComboBox>) editorComponent).removeAllItems();
((JComboBox) editorComponent).addItem(value);
@@ -207,19 +212,30 @@ public class DarkTableCellEditor extends DefaultCellEditor {
}
}
+ var rendererComp = table.getCellRenderer(row, column)
+ .getTableCellRendererComponent(table, value, isSelected, false, row, column);
+ if (rendererComp instanceof JLabel) {
+ var icon = ((JLabel) rendererComp).getIcon();
+ if (icon != null) {
+ comp = iconWrapper;
+ iconWrapper.init(editorComponent, icon, rendererComp.getComponentOrientation().isLeftToRight());
+ iconWrapper.setIconGap(((JLabel) rendererComp).getIconTextGap() - 1);
+ }
+ }
+
boolean alternativeRow = UIManager.getBoolean("Table.alternateRowColor");
Color alternativeRowColor = UIManager.getColor("Table.alternateRowBackground");
Color normalColor = UIManager.getColor("Table.background");
if (alternativeRow) {
if (!isSelected) {
if (row % 2 == 1) {
- editorComponent.setBackground(alternativeRowColor);
+ comp.setBackground(alternativeRowColor);
} else {
- editorComponent.setBackground(normalColor);
+ comp.setBackground(normalColor);
}
}
}
- return editorComponent;
+ return comp;
}
@Contract("null, _ -> false")
@@ -234,4 +250,58 @@ public class DarkTableCellEditor extends DefaultCellEditor {
}
return checkBoxEditor;
}
+
+ protected static class IconWrapper extends JPanel {
+
+ private final JLabel label;
+ private JComponent c;
+ private int iconGap;
+
+ protected IconWrapper() {
+ setLayout(null);
+ label = new JLabel();
+ label.setIconTextGap(0);
+ add(label);
+ }
+
+ protected void setIconGap(final int iconGap) {
+ this.iconGap = iconGap;
+ }
+
+ protected void init(@NotNull final JComponent component, final Icon icon, final boolean ltr) {
+ setComponentOrientation(ltr ? ComponentOrientation.LEFT_TO_RIGHT : ComponentOrientation.RIGHT_TO_LEFT);
+ if (c != null) {
+ remove(c);
+ }
+ add(component);
+ this.c = component;
+ label.setIcon(icon);
+ }
+
+ @Override
+ public void doLayout() {
+ if (c == null) return;
+ int w = getWidth();
+ int h = getHeight();
+ var b = c.getBorder();
+ var ins = new Insets(0, 0, 0, 0);
+ var labelSize = label.getPreferredSize();
+ int gap = getIconCompGap();
+ if (b != null) {
+ ins = b.getBorderInsets(c);
+ }
+ if (getComponentOrientation().isLeftToRight()) {
+ label.setBounds(ins.left + gap, 0, labelSize.width + 1, h);
+ c.setBounds(ins.left + labelSize.width + 2 * gap - 1, 0,
+ w - ins.left - labelSize.width - 2 * gap + 1, h);
+ } else {
+ c.setBounds(0, 0, w - ins.right - labelSize.width - gap - 1, h);
+ label.setBounds(w - ins.right - labelSize.width - gap - 1, 0, labelSize.width + 1, h);
+ }
+ }
+
+ public int getIconCompGap() {
+ return iconGap;
+ }
+ }
}
diff --git a/src/main/java/com/weis/darklaf/ui/table/DarkTableCellFocusBorder.java b/src/main/java/com/weis/darklaf/ui/table/DarkTableCellFocusBorder.java
index 0498be6c..cb06091f 100644
--- a/src/main/java/com/weis/darklaf/ui/table/DarkTableCellFocusBorder.java
+++ b/src/main/java/com/weis/darklaf/ui/table/DarkTableCellFocusBorder.java
@@ -15,7 +15,35 @@ public class DarkTableCellFocusBorder extends DarkCellBorder {
public void paintBorder(final Component c, final Graphics g, final int x, final int y,
final int width, final int height) {
super.paintBorder(c, g, x, y, width, height);
- g.setColor(UIManager.getColor("Table.focusBorderColor"));
- DarkUIUtil.drawRect(g, 0, 0, width, height, 1);
+ if (isRowFocusBorder(c)) {
+ g.setColor(UIManager.getColor("Table.focusRowBorderColor"));
+ ((Graphics2D) g).scale(0.5, 0.5);
+ g.drawRect(0, 0, 2 * width, 1);
+ g.drawRect(0, 2 * height - 2, 2 * width, 1);
+ if (forcePaintLeft(c)) {
+ g.drawRect(0, 0, 1, 2 * height);
+ }
+ if (forcePaintRight(c)) {
+ g.drawRect(2 * width - 2, 0, 1, 2 * height);
+ }
+ } else {
+ g.setColor(UIManager.getColor("Table.focusBorderColor"));
+ DarkUIUtil.drawRect(g, 0, 0, width, height, 1);
+ }
+ }
+
+ protected static boolean isRowFocusBorder(final Component c) {
+ return c instanceof JComponent
+ && Boolean.TRUE.equals(((JComponent) c).getClientProperty("JTable.rowFocusBorder"));
+ }
+
+ protected static boolean forcePaintLeft(final Component c) {
+ return c instanceof JComponent
+ && Boolean.TRUE.equals(((JComponent) c).getClientProperty("JTable.forcePaintLeft"));
+ }
+
+ protected static boolean forcePaintRight(final Component c) {
+ return c instanceof JComponent
+ && Boolean.TRUE.equals(((JComponent) c).getClientProperty("JTable.forcePaintRight"));
}
}
diff --git a/src/main/java/com/weis/darklaf/ui/table/DarkTableCellRenderer.java b/src/main/java/com/weis/darklaf/ui/table/DarkTableCellRenderer.java
index 1812557a..c88e6d8f 100644
--- a/src/main/java/com/weis/darklaf/ui/table/DarkTableCellRenderer.java
+++ b/src/main/java/com/weis/darklaf/ui/table/DarkTableCellRenderer.java
@@ -1,11 +1,14 @@
package com.weis.darklaf.ui.table;
import com.weis.darklaf.ui.cell.DarkCellRendererToggleButton;
+import com.weis.darklaf.util.DarkUIUtil;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import javax.swing.table.DefaultTableCellRenderer;
+import javax.swing.table.JTableHeader;
import javax.swing.table.TableCellRenderer;
+import javax.swing.table.TableColumn;
import java.awt.*;
/**
@@ -34,6 +37,27 @@ public class DarkTableCellRenderer extends DefaultTableCellRenderer {
this.setVerticalAlignment(SwingConstants.CENTER);
setHorizontalAlignment(table.getComponentOrientation().isLeftToRight() ? LEFT : RIGHT);
+ if (DarkTableCellFocusBorder.isRowFocusBorder(table)
+ && table.getSelectionModel().getLeadSelectionIndex() == row
+ && !table.isEditing()
+ && DarkUIUtil.hasFocus(table)) {
+ component.setBorder(UIManager.getBorder("Table.focusSelectedCellHighlightBorder"));
+ component.putClientProperty("JTable.rowFocusBorder", true);
+ JTableHeader header = table.getTableHeader();
+ TableColumn draggedColumn = (header == null) ? null : header.getDraggedColumn();
+ boolean forceLeft = false;
+ boolean forceRight = false;
+ if (draggedColumn != null) {
+ int index = DarkTableUI.viewIndexForColumn(draggedColumn, table);
+ forceLeft = column == index + 1 || column == index;
+ forceRight = column == index - 1 || column == index;
+ }
+ component.putClientProperty("JTable.forcePaintRight", forceRight);
+ component.putClientProperty("JTable.forcePaintLeft", forceLeft);
+ } else {
+ component.putClientProperty("JTable.rowFocusBorder", false);
+ }
+
boolean alternativeRow = UIManager.getBoolean("Table.alternateRowColor");
Color alternativeRowColor = UIManager.getColor("Table.alternateRowBackground");
Color normalColor = UIManager.getColor("Table.background");
diff --git a/src/main/java/com/weis/darklaf/ui/table/DarkTableHeaderUI.java b/src/main/java/com/weis/darklaf/ui/table/DarkTableHeaderUI.java
index 0cadeb91..88ab697c 100644
--- a/src/main/java/com/weis/darklaf/ui/table/DarkTableHeaderUI.java
+++ b/src/main/java/com/weis/darklaf/ui/table/DarkTableHeaderUI.java
@@ -164,6 +164,9 @@ public class DarkTableHeaderUI extends DarkTableHeaderUIBridge {
if (draggedColumnIndex != cMax) {
g.fillRect(draggedCellRect.x + draggedCellRect.width - 1, draggedCellRect.y,
1, draggedCellRect.height);
+ } else {
+ g.fillRect(draggedCellRect.x + draggedCellRect.width, draggedCellRect.y,
+ 1, draggedCellRect.height);
}
if (draggedColumnIndex == cMin) {
g.fillRect(draggedCellRect.x, draggedCellRect.y, 1, draggedCellRect.height);
@@ -188,6 +191,9 @@ public class DarkTableHeaderUI extends DarkTableHeaderUIBridge {
if (draggedColumnIndex != cMax) {
g.fillRect(draggedCellRect.x + draggedCellRect.width - 1, draggedCellRect.y,
1, draggedCellRect.height);
+ } else {
+ g.fillRect(draggedCellRect.x + draggedCellRect.width, draggedCellRect.y,
+ 1, draggedCellRect.height);
}
} else {
if (draggedColumnIndex != cMin) {
diff --git a/src/main/java/com/weis/darklaf/ui/table/DarkTableUI.java b/src/main/java/com/weis/darklaf/ui/table/DarkTableUI.java
index 5f88f4e9..9db169ee 100644
--- a/src/main/java/com/weis/darklaf/ui/table/DarkTableUI.java
+++ b/src/main/java/com/weis/darklaf/ui/table/DarkTableUI.java
@@ -1,5 +1,6 @@
package com.weis.darklaf.ui.table;
+import com.weis.darklaf.util.DarkUIUtil;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import sun.swing.SwingUtilities2;
@@ -11,9 +12,12 @@ import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import java.awt.*;
+import java.awt.event.ActionEvent;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
+import java.awt.event.MouseEvent;
import java.beans.PropertyChangeListener;
+import java.util.function.Supplier;
/**
* @author Jannis Weis
@@ -52,6 +56,22 @@ public class DarkTableUI extends DarkTableUIBridge {
} else if ("showVerticalLines".equals(key)) {
var b = (boolean) e.getNewValue();
table.getColumnModel().setColumnMargin(b ? 1 : 0);
+ } else if ("ancestor".equals(key)) {
+ var oldVal = e.getOldValue();
+ var newVal = e.getNewValue();
+ if (oldVal instanceof Component) {
+ var oldUnwrapped = SwingUtilities.getUnwrappedParent((Component) oldVal);
+ if ((oldUnwrapped instanceof JScrollPane)
+ && ((JComponent) oldUnwrapped).getBorder() instanceof UIResource) {
+ LookAndFeel.uninstallBorder((JComponent) oldUnwrapped);
+ }
+ }
+ if (newVal instanceof Component) {
+ var newUnwrapped = SwingUtilities.getUnwrappedParent((Component) newVal);
+ if ((newUnwrapped instanceof JScrollPane)) {
+ LookAndFeel.installBorder((JComponent) newUnwrapped, "Table.scrollPaneBorder");
+ }
+ }
}
};
@@ -61,53 +81,6 @@ public class DarkTableUI extends DarkTableUIBridge {
return new DarkTableUI();
}
- @Override
- protected void installDefaults() {
- super.installDefaults();
- table.setRowHeight(ROW_HEIGHT);
- table.setDefaultEditor(Object.class, new DarkTableCellEditor());
- table.putClientProperty("JTable.renderBooleanAsCheckBox",
- UIManager.getBoolean("Table.renderBooleanAsCheckBox"));
- table.putClientProperty("JTable.booleanRenderType", UIManager.getString("Table.booleanRenderType"));
- setupRendererComponents(table);
- }
-
- protected static void setupRendererComponents(@NotNull final JTable table) {
- var cellRenderer = new DarkTableCellRenderer();
- var cellEditor = new DarkTableCellEditor();
- var colorRendererEditor = new DarkColorTableCellRendererEditor();
-
- table.setDefaultRenderer(Object.class, cellRenderer);
- table.setDefaultRenderer(String.class, cellRenderer);
- table.setDefaultRenderer(Integer.class, cellRenderer);
- table.setDefaultRenderer(Double.class, cellRenderer);
- table.setDefaultRenderer(Float.class, cellRenderer);
- table.setDefaultRenderer(Boolean.class, cellRenderer);
- table.setDefaultRenderer(Color.class, colorRendererEditor);
-
- table.setDefaultEditor(Object.class, cellEditor);
- table.setDefaultEditor(String.class, cellEditor);
- table.setDefaultEditor(Integer.class, cellEditor);
- table.setDefaultEditor(Double.class, cellEditor);
- table.setDefaultEditor(Float.class, cellEditor);
- table.setDefaultEditor(Boolean.class, cellEditor);
- table.setDefaultEditor(Color.class, colorRendererEditor);
- }
-
- @Override
- protected void installListeners() {
- super.installListeners();
- table.addFocusListener(focusListener);
- table.addPropertyChangeListener(propertyChangeListener);
- }
-
- @Override
- protected void uninstallListeners() {
- super.uninstallListeners();
- table.removeFocusListener(focusListener);
- table.removePropertyChangeListener(propertyChangeListener);
- }
-
@Override
protected void paintGrid(@NotNull final Graphics g,
final int rMin, final int rMax, final int cMin, final int cMax) {
@@ -140,7 +113,7 @@ public class DarkTableUI extends DarkTableUIBridge {
boolean scrollVisible = scrollBarVisible();
if (table.getShowVerticalLines()) {
TableColumnModel cm = table.getColumnModel();
- int tableHeight = damagedArea.y + damagedArea.height;
+ int tableHeight = getPreferredSize(table).height;
int x;
boolean ltr = table.getComponentOrientation().isLeftToRight();
if (ltr) {
@@ -169,16 +142,129 @@ public class DarkTableUI extends DarkTableUIBridge {
}
}
}
- if (!table.getShowHorizontalLines() && table.getRowCount() != 0 && !scrollVisible) {
- g.setColor(getBorderColor());
- var clip = g.getClipBounds();
- clip.height += 1;
- g.setClip(clip);
- int y = table.getHeight();
- g.fillRect(0, y, table.getWidth(), 1);
+ }
+
+
+ protected static void setupRendererComponents(@NotNull final JTable table) {
+ var cellRenderer = new DarkTableCellRenderer();
+ var cellEditor = new DarkTableCellEditor();
+ var colorRendererEditor = new DarkColorTableCellRendererEditor();
+
+ table.setDefaultRenderer(Object.class, cellRenderer);
+ table.setDefaultRenderer(String.class, cellRenderer);
+ table.setDefaultRenderer(Integer.class, cellRenderer);
+ table.setDefaultRenderer(Double.class, cellRenderer);
+ table.setDefaultRenderer(Float.class, cellRenderer);
+ table.setDefaultRenderer(Boolean.class, cellRenderer);
+ table.setDefaultRenderer(Color.class, colorRendererEditor);
+
+ table.setDefaultEditor(Object.class, cellEditor);
+ table.setDefaultEditor(String.class, cellEditor);
+ table.setDefaultEditor(Integer.class, cellEditor);
+ table.setDefaultEditor(Double.class, cellEditor);
+ table.setDefaultEditor(Float.class, cellEditor);
+ table.setDefaultEditor(Boolean.class, cellEditor);
+ table.setDefaultEditor(Color.class, colorRendererEditor);
+ }
+
+ @Override
+ protected void paintDraggedArea(@NotNull final Graphics g, final int rMin, final int rMax,
+ final int cMin, final int cMax,
+ final TableColumn draggedColumn, final int distance) {
+ int draggedColumnIndex = viewIndexForColumn(draggedColumn);
+
+ Rectangle minCell = table.getCellRect(rMin, draggedColumnIndex, true);
+ Rectangle maxCell = table.getCellRect(rMax, draggedColumnIndex, true);
+
+ Rectangle vacatedColumnRect = minCell.union(maxCell);
+
+ int dist = adjustDistance(distance, vacatedColumnRect, table);
+
+ // Paint a gray well in place of the moving column.
+ g.setColor(table.getParent().getBackground());
+ g.fillRect(vacatedColumnRect.x, vacatedColumnRect.y,
+ vacatedColumnRect.width - 1, vacatedColumnRect.height);
+
+
+ // Move to the where the cell has been dragged.
+ vacatedColumnRect.x += dist;
+
+ boolean scrollVisible = scrollBarVisible();
+ boolean drawBottomBorder = !table.getShowHorizontalLines() && !scrollVisible && table.getShowVerticalLines();
+ boolean ltr = table.getComponentOrientation().isLeftToRight();
+
+ // Fill the background.
+ g.setColor(table.getBackground());
+ g.fillRect(vacatedColumnRect.x, vacatedColumnRect.y,
+ vacatedColumnRect.width, vacatedColumnRect.height);
+
+
+ // Paint the vertical grid lines if necessary.
+ if (table.getShowVerticalLines()) {
+ g.setColor(table.getGridColor());
+ int x1 = vacatedColumnRect.x;
+ int y1 = vacatedColumnRect.y;
+ int x2 = x1 + vacatedColumnRect.width - 1;
+ int y2 = y1 + vacatedColumnRect.height - 1;
+
+ boolean onLeftEdge = ltr ? draggedColumnIndex == cMin : draggedColumnIndex == cMax;
+ boolean onRightEdge = ltr ? draggedColumnIndex == cMax : draggedColumnIndex == cMin;
+ if (scrollBarVisible()) {
+ if (isScrollPaneRtl()) {
+ onLeftEdge = false;
+ } else {
+ onRightEdge = false;
+ }
+ }
+ // Left
+ if (dist != 0 || !onLeftEdge) {
+ if (draggedColumnIndex == cMin && scrollBarVisible() && isScrollPaneRtl()) x1++;
+ g.fillRect(x1 - 1, y1, 1, y2 - y1);
+ }
+ // Right
+ if (dist != 0 || !onRightEdge) {
+ g.fillRect(x2, y1, 1, y2 - y1);
+ }
+ }
+
+ for (int row = rMin; row <= rMax; row++) {
+ // Render the cell value
+ Rectangle r = table.getCellRect(row, draggedColumnIndex, false);
+ r.x += dist;
+ paintCell(g, r, row, draggedColumnIndex);
+
+ // Paint the (lower) horizontal grid line if necessary.
+ if (table.getShowHorizontalLines() || (!scrollVisible && row == rMax)) {
+ g.setColor(table.getGridColor());
+ Rectangle rcr = table.getCellRect(row, draggedColumnIndex, true);
+ rcr.x += dist;
+ int x1 = rcr.x - 1;
+ int y1 = rcr.y;
+ int x2 = x1 + rcr.width + 1;
+ int y2 = y1 + rcr.height - 1;
+ g.fillRect(x1, y2, x2 - x1, 1);
+ }
}
}
+ @Override
+ protected void installListeners() {
+ super.installListeners();
+ table.addFocusListener(focusListener);
+ table.addPropertyChangeListener(propertyChangeListener);
+ }
+
+ @Override
+ protected void uninstallListeners() {
+ super.uninstallListeners();
+ table.removeFocusListener(focusListener);
+ table.removePropertyChangeListener(propertyChangeListener);
+ }
+
+ protected boolean pointOutsidePrefSize(final int row, final int column, final Point p) {
+ return false;
+ }
+
protected boolean isScrollPaneRtl() {
if (!isInScrollPane()) return false;
Container comp = SwingUtilities.getUnwrappedParent(table).getParent();
@@ -300,91 +386,94 @@ public class DarkTableUI extends DarkTableUIBridge {
}
@Override
- protected void paintDraggedArea(@NotNull final Graphics g, final int rMin, final int rMax,
- final int cMin, final int cMax,
- final TableColumn draggedColumn, final int distance) {
- int draggedColumnIndex = viewIndexForColumn(draggedColumn);
-
- Rectangle minCell = table.getCellRect(rMin, draggedColumnIndex, true);
- Rectangle maxCell = table.getCellRect(rMax, draggedColumnIndex, true);
-
- Rectangle vacatedColumnRect = minCell.union(maxCell);
-
- int dist = adjustDistance(distance, vacatedColumnRect, table);
-
- // Paint a gray well in place of the moving column.
- g.setColor(table.getParent().getBackground());
- g.fillRect(vacatedColumnRect.x, vacatedColumnRect.y,
- vacatedColumnRect.width - 1, vacatedColumnRect.height);
-
-
- // Move to the where the cell has been dragged.
- vacatedColumnRect.x += dist;
+ protected Handler getHandler() {
+ if (handler == null) {
+ handler = new DarkHandler();
+ }
+ return handler;
+ }
- boolean scrollVisible = scrollBarVisible();
- boolean drawBottomBorder = !table.getShowHorizontalLines() && !scrollVisible;
- boolean ltr = table.getComponentOrientation().isLeftToRight();
+ @Override
+ protected void installDefaults() {
+ super.installDefaults();
+ int rowHeight = UIManager.getInt("Table.rowHeight");
+ if (rowHeight > 0) {
+ table.setRowHeight(ROW_HEIGHT);
+ }
+ table.setDefaultEditor(Object.class, new DarkTableCellEditor());
+ table.putClientProperty("JTable.renderBooleanAsCheckBox",
+ UIManager.getBoolean("Table.renderBooleanAsCheckBox"));
+ table.putClientProperty("JTable.booleanRenderType", UIManager.getString("Table.booleanRenderType"));
+ setupRendererComponents(table);
+ }
- // Fill the background.
- g.setColor(table.getBackground());
- g.fillRect(vacatedColumnRect.x, vacatedColumnRect.y,
- vacatedColumnRect.width, vacatedColumnRect.height);
+ @Override
+ public Dimension getPreferredSize(final JComponent c) {
+ var prefSize = super.getPreferredSize(c);
+ if (!isInScrollPane()) {
+ return prefSize;
+ } else {
+ var dim = SwingUtilities.getUnwrappedParent(table).getSize();
+ if (dim.width < prefSize.width || dim.height < prefSize.height) {
+ return prefSize;
+ } else {
+ return dim;
+ }
+ }
+ }
+ protected class DarkHandler extends Handler {
- // Paint the vertical grid lines if necessary.
- if (table.getShowVerticalLines()) {
- g.setColor(table.getGridColor());
- int x1 = vacatedColumnRect.x;
- int y1 = vacatedColumnRect.y;
- int x2 = x1 + vacatedColumnRect.width - 1;
- int y2 = y1 + vacatedColumnRect.height - 1;
+ protected int lastIndex = -1;
- boolean onLeftEdge = ltr ? draggedColumnIndex == cMin : draggedColumnIndex == cMax;
- boolean onRightEdge = ltr ? draggedColumnIndex == cMax : draggedColumnIndex == cMin;
- if (scrollBarVisible()) {
- if (isScrollPaneRtl()) {
- onLeftEdge = false;
+ @Override
+ public void mouseClicked(final MouseEvent e) {
+ super.mouseClicked(e);
+ if (isFileList) {
+ int row = table.rowAtPoint(e.getPoint());
+ JFileChooser fc = getFileChooser();
+ if (row < 0 || fc == null) return;
+ int column = getFileNameColumnIndex();
+ boolean isSelected = table.getSelectionModel().getLeadSelectionIndex() == row
+ && table.getColumnModel().getSelectionModel().getLeadSelectionIndex() == column;
+ if ((!fc.isMultiSelectionEnabled() || fc.getSelectedFiles().length <= 1)
+ && isSelected && lastIndex == row
+ && DarkUIUtil.isOverText(e, row, column, table)) {
+ startEditing(row, column);
} else {
- onRightEdge = false;
+ lastIndex = row;
}
}
- // Left
- if (dist != 0 || !onLeftEdge) {
- if (draggedColumnIndex == cMin && scrollBarVisible() && isScrollPaneRtl()) x1++;
- g.fillRect(x1 - 1, y1, 1, y2 - y1);
- }
- // Right
- if (dist != 0 || !onRightEdge) {
- g.fillRect(x2, y1, 1, y2 - y1);
+ }
+
+ protected JFileChooser getFileChooser() {
+ var obj = table.getClientProperty("JTable.fileChooserParent");
+ if (obj instanceof Supplier) {
+ var supplied = ((Supplier) obj).get();
+ return supplied instanceof JFileChooser ? (JFileChooser) supplied : null;
}
+ return null;
}
- for (int row = rMin; row <= rMax; row++) {
- // Render the cell value
- Rectangle r = table.getCellRect(row, draggedColumnIndex, false);
- r.x += dist;
- paintCell(g, r, row, draggedColumnIndex);
+ protected Integer getFileNameColumnIndex() {
+ var obj = table.getClientProperty("JTable.fileNameColumnIndex");
+ return obj instanceof Integer ? (Integer) obj : 0;
+ }
- // Paint the (lower) horizontal grid line if necessary.
- if (table.getShowHorizontalLines() || (!scrollVisible && row == rMax)) {
- g.setColor(table.getGridColor());
- if (drawBottomBorder) {
- g.setColor(getBorderColor());
- }
- Rectangle rcr = table.getCellRect(row, draggedColumnIndex, true);
- rcr.x += dist;
- int x1 = rcr.x - 1;
- int y1 = rcr.y;
- int x2 = x1 + rcr.width + 1;
- int y2 = y1 + rcr.height - 1;
- g.fillRect(x1, y2, x2 - x1, 1);
+ protected void startEditing(final int row, final int column) {
+ table.editCellAt(row, column, null);
+ Component editorComponent = table.getEditorComponent();
+ if (editorComponent != null && !editorComponent.hasFocus()) {
+ SwingUtilities2.compositeRequestFocus(editorComponent);
}
}
- if (drawBottomBorder) {
- var rect = table.getCellRect(rMax, draggedColumnIndex, true);
- int y = rect.y + rect.height - 1;
- g.setColor(getBorderColor());
- g.fillRect(rect.x, y, rect.width, 1);
+
+ @Override
+ protected void maybeStartTimer() {
+ }
+
+ @Override
+ public void actionPerformed(final ActionEvent ae) {
}
}
}
diff --git a/src/main/java/com/weis/darklaf/ui/table/DarkTableUIBridge.java b/src/main/java/com/weis/darklaf/ui/table/DarkTableUIBridge.java
index e52824b3..d9245cf4 100644
--- a/src/main/java/com/weis/darklaf/ui/table/DarkTableUIBridge.java
+++ b/src/main/java/com/weis/darklaf/ui/table/DarkTableUIBridge.java
@@ -27,7 +27,6 @@ import org.jetbrains.annotations.NotNull;
import sun.swing.SwingUtilities2;
import javax.swing.*;
-import javax.swing.plaf.basic.BasicTableUI;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
@@ -37,21 +36,7 @@ import java.awt.*;
/**
* @author Jannis Weis
*/
-public class DarkTableUIBridge extends BasicTableUI {
-
- protected static int getAdjustedLead(final JTable table, final boolean row) {
- return row ? getAdjustedLead(table, true, table.getSelectionModel())
- : getAdjustedLead(table, false, table.getColumnModel().getSelectionModel());
- }
-
- protected static int getAdjustedLead(final JTable table,
- final boolean row,
- final ListSelectionModel model) {
-
- int index = model.getLeadSelectionIndex();
- int compare = row ? table.getRowCount() : table.getColumnCount();
- return index < compare ? index : -1;
- }
+public class DarkTableUIBridge extends BasicTableUIBridge {
/**
* Paint a representation of the table
instance
@@ -446,6 +431,10 @@ public class DarkTableUIBridge extends BasicTableUI {
}
protected int viewIndexForColumn(final TableColumn aColumn) {
+ return viewIndexForColumn(aColumn, table);
+ }
+
+ public static int viewIndexForColumn(final TableColumn aColumn, final JTable table) {
TableColumnModel cm = table.getColumnModel();
for (int column = 0; column < cm.getColumnCount(); column++) {
if (cm.getColumn(column) == aColumn) {
diff --git a/src/main/java/com/weis/darklaf/ui/table/TextFieldTableCellEditorBorder.java b/src/main/java/com/weis/darklaf/ui/table/TextFieldTableCellEditorBorder.java
index 53c89bf2..8a50dc0a 100644
--- a/src/main/java/com/weis/darklaf/ui/table/TextFieldTableCellEditorBorder.java
+++ b/src/main/java/com/weis/darklaf/ui/table/TextFieldTableCellEditorBorder.java
@@ -1,6 +1,8 @@
package com.weis.darklaf.ui.table;
import com.weis.darklaf.ui.text.DarkTextFieldUI;
+import com.weis.darklaf.util.DarkUIUtil;
+import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
@@ -12,12 +14,11 @@ import java.awt.*;
public class TextFieldTableCellEditorBorder extends DarkTableCellBorder {
@Override
- public void paintBorder(final Component c, @NotNull final Graphics g, final int x, final int y,
+ public void paintBorder(@NotNull final Component c, @NotNull final Graphics g, final int x, final int y,
final int width, final int height) {
- g.setColor(DarkTextFieldUI.getBorderColor(c));
- var parent = c.getParent();
- if (parent instanceof JTable) {
- var table = ((JTable) parent);
+ g.setColor(DarkTextFieldUI.getBorderColor(false, false, true, true));
+ var table = DarkUIUtil.getParentOfType(JTable.class, c);
+ if (table != null) {
if (!table.getShowHorizontalLines()) {
g.fillRect(0, 0, width, 1);
g.fillRect(0, height - 1, width, 1);
@@ -25,7 +26,53 @@ public class TextFieldTableCellEditorBorder extends DarkTableCellBorder {
if (!table.getShowVerticalLines()) {
g.fillRect(0, 0, 1, height);
g.fillRect(width - 1, 0, 1, height);
+ } else if (isInWrapper(c)) {
+ if (c.getParent().getComponentOrientation().isLeftToRight()) {
+ g.fillRect(0, 0, 1, height);
+ } else {
+ g.fillRect(width - 1, 0, 1, height);
+ }
}
+ } else {
+ DarkUIUtil.drawRect(g, x, y, width, height, 1);
}
}
+
+ protected static boolean isInWrapper(@NotNull final Component c) {
+ return c.getParent() instanceof DarkTableCellEditor.IconWrapper;
+ }
+
+ @Override
+ public Insets getBorderInsets(final Component c) {
+ var ins = super.getBorderInsets();
+ if (isInWrapper(c)) {
+ if (parentLTR(c)) {
+ ins.left -= ((DarkTableCellEditor.IconWrapper) c.getParent()).getIconCompGap();
+ } else {
+ ins.right -= ((DarkTableCellEditor.IconWrapper) c.getParent()).getIconCompGap();
+ }
+ } else if (isListEditor(c)) {
+ var renderer = ((JList) c.getParent()).getCellRenderer();
+ if (renderer instanceof JLabel) {
+ if (parentLTR(c)) {
+ ins.left -= ((JLabel) renderer).getIconTextGap() - 1;
+ } else {
+ ins.right -= ((JLabel) renderer).getIconTextGap() - 1;
+ }
+ }
+ }
+ return ins;
+ }
+
+ protected static boolean parentLTR(@NotNull final Component c) {
+ return c.getParent().getComponentOrientation().isLeftToRight();
+ }
+
+ @Contract("null -> false")
+ protected static boolean isListEditor(final Component c) {
+ return c instanceof JComponent
+ && Boolean.TRUE.equals(((JComponent) c).getClientProperty("JTextField.listCellEditor"))
+ && c.getParent() instanceof JList;
+ }
+
}
diff --git a/src/main/java/com/weis/darklaf/ui/text/DarkTextBorder.java b/src/main/java/com/weis/darklaf/ui/text/DarkTextBorder.java
index a2463303..81c30316 100644
--- a/src/main/java/com/weis/darklaf/ui/text/DarkTextBorder.java
+++ b/src/main/java/com/weis/darklaf/ui/text/DarkTextBorder.java
@@ -23,6 +23,7 @@
*/
package com.weis.darklaf.ui.text;
+import com.weis.darklaf.ui.table.TextFieldTableCellEditorBorder;
import com.weis.darklaf.util.DarkUIUtil;
import com.weis.darklaf.util.GraphicsContext;
import com.weis.darklaf.util.GraphicsUtil;
@@ -38,11 +39,18 @@ import java.awt.*;
*/
public class DarkTextBorder implements Border, UIResource {
+ private static final Border editorBorder = new TextFieldTableCellEditorBorder();
+
public final static int BORDER_SIZE = 3;
public final static int PADDING = 4;
public void paintBorder(final Component c, final Graphics g2, final int x, final int y,
final int width, final int height) {
+ if (isCellEditor(c)) {
+ editorBorder.paintBorder(c, g2, x, y, width, height);
+ return;
+ }
+
Graphics2D g = (Graphics2D) g2;
g.translate(x, y);
GraphicsContext config = GraphicsUtil.setupStrokePainting(g);
@@ -76,8 +84,16 @@ public class DarkTextBorder implements Border, UIResource {
config.restore();
}
+ protected static boolean isCellEditor(final Component c) {
+ return c instanceof JComponent
+ && Boolean.TRUE.equals(((JComponent) c).getClientProperty("JTextField.cellEditor"));
+ }
+
@Override
public Insets getBorderInsets(final Component c) {
+ if (isCellEditor(c)) {
+ return editorBorder.getBorderInsets(c);
+ }
Insets insets = new Insets(BORDER_SIZE + PADDING, BORDER_SIZE + PADDING,
BORDER_SIZE + PADDING, BORDER_SIZE + PADDING);
if (DarkTextFieldUI.isSearchField(c)) {
diff --git a/src/main/java/com/weis/darklaf/ui/text/DarkTextFieldUI.java b/src/main/java/com/weis/darklaf/ui/text/DarkTextFieldUI.java
index 0706134e..6d2886d2 100644
--- a/src/main/java/com/weis/darklaf/ui/text/DarkTextFieldUI.java
+++ b/src/main/java/com/weis/darklaf/ui/text/DarkTextFieldUI.java
@@ -95,6 +95,11 @@ public class DarkTextFieldUI extends DarkTextFieldUIBridge implements PropertyCh
boolean editable = !(c instanceof JTextComponent) || ((JTextComponent) c).isEditable();
boolean focus = DarkUIUtil.hasFocus(c);
boolean error = hasError(c);
+ return getBorderColor(focus, error, editable, c.isEnabled());
+ }
+
+ public static Color getBorderColor(@NotNull final boolean focus, final boolean error,
+ final boolean editable, final boolean enabled) {
if (focus) {
if (error) {
return UIManager.getColor("TextField.border.focusError");
@@ -104,7 +109,7 @@ public class DarkTextFieldUI extends DarkTextFieldUIBridge implements PropertyCh
} else if (error) {
return UIManager.getColor("TextField.border.error");
}
- return c.isEnabled() && editable
+ return enabled && editable
? UIManager.getColor("TextField.border.enabled")
: UIManager.getColor("TextField.border.disabled");
}
diff --git a/src/main/java/com/weis/darklaf/ui/tooltip/DarkTooltipBorder.java b/src/main/java/com/weis/darklaf/ui/tooltip/DarkTooltipBorder.java
index 85cdc6bd..8d5a452a 100644
--- a/src/main/java/com/weis/darklaf/ui/tooltip/DarkTooltipBorder.java
+++ b/src/main/java/com/weis/darklaf/ui/tooltip/DarkTooltipBorder.java
@@ -82,6 +82,8 @@ public class DarkTooltipBorder implements Border, UIResource {
si.right = 0;
} else if (align == Alignment.WEST) {
si.left = 0;
+ } else if (align == Alignment.NORTH_EAST || align == Alignment.NORTH || align == Alignment.NORTH_WEST) {
+ si.bottom = 0;
}
}
diff --git a/src/main/java/com/weis/darklaf/util/DarkUIUtil.java b/src/main/java/com/weis/darklaf/util/DarkUIUtil.java
index f852d56d..6c35d947 100644
--- a/src/main/java/com/weis/darklaf/util/DarkUIUtil.java
+++ b/src/main/java/com/weis/darklaf/util/DarkUIUtil.java
@@ -37,6 +37,8 @@ import javax.swing.table.TableCellRenderer;
import javax.swing.tree.TreeCellRenderer;
import java.awt.*;
import java.awt.event.ActionEvent;
+import java.awt.event.InputEvent;
+import java.awt.event.MouseEvent;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Path2D;
import java.awt.geom.RoundRectangle2D;
@@ -166,13 +168,7 @@ public final class DarkUIUtil {
return new Color(redPart, greenPart, bluePart);
}
- public static void drawRect(final Graphics g, final int x, final int y, final int width, final int height,
- final int thickness) {
- g.fillRect(x, y, width, thickness);
- g.fillRect(x, y, thickness, height);
- g.fillRect(x + width - thickness, y, thickness, height);
- g.fillRect(x, y + height - thickness, width, thickness);
- }
+ private static final Rectangle iconRect = new Rectangle();
public static void applyInsets(final Rectangle rect, final Insets insets) {
if (insets != null && rect != null) {
@@ -316,6 +312,59 @@ public final class DarkUIUtil {
return null;
}
+ private static final Rectangle textRect = new Rectangle();
+
+ public static void drawRect(@NotNull final Graphics g, final int x, final int y,
+ final int width, final int height, final int thickness) {
+ g.fillRect(x, y, width, thickness);
+ g.fillRect(x, y + thickness, thickness, height - 2 * thickness);
+ g.fillRect(x + width - thickness, y + thickness, thickness, height - 2 * thickness);
+ g.fillRect(x, y + height - thickness, width, thickness);
+ }
+
+ public static boolean isOverText(@NotNull final MouseEvent e, final int index, final JList list) {
+ var bounds = list.getCellBounds(index, index);
+ if (!bounds.contains(e.getPoint())) return false;
+ //noinspection unchecked
+ var cellRenderer = ((ListCellRenderer) list.getCellRenderer())
+ .getListCellRendererComponent(list, list.getModel().getElementAt(index),
+ index, false, false);
+ if (cellRenderer instanceof JLabel) {
+ return isOverText((JLabel) cellRenderer, bounds, e.getPoint());
+ } else {
+ return true;
+ }
+ }
+
+ public static boolean isOverText(final JLabel label, final Rectangle bounds, final Point p) {
+ textRect.setBounds(0, 0, 0, 0);
+ iconRect.setBounds(0, 0, 0, 0);
+ SwingUtilities.layoutCompoundLabel(label, label.getFontMetrics(label.getFont()), label.getText(),
+ label.getIcon(), label.getVerticalAlignment(),
+ label.getHorizontalAlignment(),
+ label.getVerticalTextPosition(), label.getHorizontalTextPosition(),
+ bounds, iconRect, textRect, label.getIconTextGap());
+ return textRect.contains(p);
+ }
+
+ public static boolean isOverText(@NotNull final MouseEvent e, final int row, final int column,
+ final JTable table) {
+ var bounds = table.getCellRect(row, column, false);
+ if (!bounds.contains(e.getPoint())) return false;
+ var cellRenderer = table.getCellRenderer(row, column).getTableCellRendererComponent(
+ table, table.getValueAt(row, column), false, false, row, column);
+ if (cellRenderer instanceof JLabel) {
+ return isOverText((JLabel) cellRenderer, bounds, e.getPoint());
+ } else {
+ return true;
+ }
+ }
+
+ public static boolean isMenuShortcutKeyDown(final InputEvent event) {
+ return (event.getModifiersEx() &
+ Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx()) != 0;
+ }
+
public enum Outline {
error {
@Override
diff --git a/src/main/resources/com/weis/darklaf/icons/dark/files/drive.svg b/src/main/resources/com/weis/darklaf/icons/dark/files/drive.svg
new file mode 100644
index 00000000..8f2c4455
--- /dev/null
+++ b/src/main/resources/com/weis/darklaf/icons/dark/files/drive.svg
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/com/weis/darklaf/icons/dark/menu/down.svg b/src/main/resources/com/weis/darklaf/icons/dark/menu/down.svg
new file mode 100644
index 00000000..40cfbf37
--- /dev/null
+++ b/src/main/resources/com/weis/darklaf/icons/dark/menu/down.svg
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/src/main/resources/com/weis/darklaf/icons/dark/menu/save.svg b/src/main/resources/com/weis/darklaf/icons/dark/menu/save.svg
new file mode 100644
index 00000000..20ed7c88
--- /dev/null
+++ b/src/main/resources/com/weis/darklaf/icons/dark/menu/save.svg
@@ -0,0 +1,4 @@
+
+
+
diff --git a/src/main/resources/com/weis/darklaf/icons/dark/menu/up.svg b/src/main/resources/com/weis/darklaf/icons/dark/menu/up.svg
new file mode 100644
index 00000000..d1682d2e
--- /dev/null
+++ b/src/main/resources/com/weis/darklaf/icons/dark/menu/up.svg
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/src/main/resources/com/weis/darklaf/icons/dark/navigation/arrowDownHover.svg b/src/main/resources/com/weis/darklaf/icons/dark/navigation/arrowDownHover.svg
new file mode 100644
index 00000000..1ad4f5e9
--- /dev/null
+++ b/src/main/resources/com/weis/darklaf/icons/dark/navigation/arrowDownHover.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/main/resources/com/weis/darklaf/icons/dark/navigation/arrowDownSelected.svg b/src/main/resources/com/weis/darklaf/icons/dark/navigation/arrowDownSelected.svg
new file mode 100644
index 00000000..dcb01140
--- /dev/null
+++ b/src/main/resources/com/weis/darklaf/icons/dark/navigation/arrowDownSelected.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/main/resources/com/weis/darklaf/icons/dark/navigation/arrowLeftHover.svg b/src/main/resources/com/weis/darklaf/icons/dark/navigation/arrowLeftHover.svg
new file mode 100644
index 00000000..202f9025
--- /dev/null
+++ b/src/main/resources/com/weis/darklaf/icons/dark/navigation/arrowLeftHover.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/main/resources/com/weis/darklaf/icons/dark/navigation/arrowLeftSelected.svg b/src/main/resources/com/weis/darklaf/icons/dark/navigation/arrowLeftSelected.svg
new file mode 100644
index 00000000..36a33e0a
--- /dev/null
+++ b/src/main/resources/com/weis/darklaf/icons/dark/navigation/arrowLeftSelected.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/main/resources/com/weis/darklaf/icons/dark/navigation/arrowRightHover.svg b/src/main/resources/com/weis/darklaf/icons/dark/navigation/arrowRightHover.svg
new file mode 100644
index 00000000..ee411c48
--- /dev/null
+++ b/src/main/resources/com/weis/darklaf/icons/dark/navigation/arrowRightHover.svg
@@ -0,0 +1,4 @@
+
+
+
diff --git a/src/main/resources/com/weis/darklaf/icons/dark/navigation/arrowRightSelected.svg b/src/main/resources/com/weis/darklaf/icons/dark/navigation/arrowRightSelected.svg
new file mode 100644
index 00000000..9cb57a3c
--- /dev/null
+++ b/src/main/resources/com/weis/darklaf/icons/dark/navigation/arrowRightSelected.svg
@@ -0,0 +1,4 @@
+
+
+
diff --git a/src/main/resources/com/weis/darklaf/icons/dark/navigation/arrowUpHover.svg b/src/main/resources/com/weis/darklaf/icons/dark/navigation/arrowUpHover.svg
new file mode 100644
index 00000000..006b43aa
--- /dev/null
+++ b/src/main/resources/com/weis/darklaf/icons/dark/navigation/arrowUpHover.svg
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/src/main/resources/com/weis/darklaf/icons/dark/navigation/arrowUpSelected.svg b/src/main/resources/com/weis/darklaf/icons/dark/navigation/arrowUpSelected.svg
new file mode 100644
index 00000000..0b6368f5
--- /dev/null
+++ b/src/main/resources/com/weis/darklaf/icons/dark/navigation/arrowUpSelected.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/main/resources/com/weis/darklaf/icons/light/files/drive.svg b/src/main/resources/com/weis/darklaf/icons/light/files/drive.svg
new file mode 100644
index 00000000..8a776df5
--- /dev/null
+++ b/src/main/resources/com/weis/darklaf/icons/light/files/drive.svg
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/com/weis/darklaf/icons/light/files/folder.svg b/src/main/resources/com/weis/darklaf/icons/light/files/folder.svg
index f2b64f37..c346241d 100644
--- a/src/main/resources/com/weis/darklaf/icons/light/files/folder.svg
+++ b/src/main/resources/com/weis/darklaf/icons/light/files/folder.svg
@@ -1,7 +1,3 @@
-
-
-
-
diff --git a/src/main/resources/com/weis/darklaf/icons/light/menu/down.svg b/src/main/resources/com/weis/darklaf/icons/light/menu/down.svg
new file mode 100644
index 00000000..826a5a6b
--- /dev/null
+++ b/src/main/resources/com/weis/darklaf/icons/light/menu/down.svg
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/src/main/resources/com/weis/darklaf/icons/light/menu/save.svg b/src/main/resources/com/weis/darklaf/icons/light/menu/save.svg
new file mode 100644
index 00000000..c1bee39e
--- /dev/null
+++ b/src/main/resources/com/weis/darklaf/icons/light/menu/save.svg
@@ -0,0 +1,4 @@
+
+
+
diff --git a/src/main/resources/com/weis/darklaf/icons/light/menu/up.svg b/src/main/resources/com/weis/darklaf/icons/light/menu/up.svg
new file mode 100644
index 00000000..268c4cc5
--- /dev/null
+++ b/src/main/resources/com/weis/darklaf/icons/light/menu/up.svg
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/src/main/resources/com/weis/darklaf/icons/light/navigation/arrowDownHover.svg b/src/main/resources/com/weis/darklaf/icons/light/navigation/arrowDownHover.svg
new file mode 100644
index 00000000..c5e2808e
--- /dev/null
+++ b/src/main/resources/com/weis/darklaf/icons/light/navigation/arrowDownHover.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/main/resources/com/weis/darklaf/icons/light/navigation/arrowDownSelected.svg b/src/main/resources/com/weis/darklaf/icons/light/navigation/arrowDownSelected.svg
new file mode 100644
index 00000000..856b4a7d
--- /dev/null
+++ b/src/main/resources/com/weis/darklaf/icons/light/navigation/arrowDownSelected.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/main/resources/com/weis/darklaf/icons/light/navigation/arrowLeftHover.svg b/src/main/resources/com/weis/darklaf/icons/light/navigation/arrowLeftHover.svg
new file mode 100644
index 00000000..1c45d361
--- /dev/null
+++ b/src/main/resources/com/weis/darklaf/icons/light/navigation/arrowLeftHover.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/main/resources/com/weis/darklaf/icons/light/navigation/arrowLeftSelected.svg b/src/main/resources/com/weis/darklaf/icons/light/navigation/arrowLeftSelected.svg
new file mode 100644
index 00000000..bcbbad2c
--- /dev/null
+++ b/src/main/resources/com/weis/darklaf/icons/light/navigation/arrowLeftSelected.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/main/resources/com/weis/darklaf/icons/light/navigation/arrowRightHover.svg b/src/main/resources/com/weis/darklaf/icons/light/navigation/arrowRightHover.svg
new file mode 100644
index 00000000..2d4cdeb4
--- /dev/null
+++ b/src/main/resources/com/weis/darklaf/icons/light/navigation/arrowRightHover.svg
@@ -0,0 +1,4 @@
+
+
+
diff --git a/src/main/resources/com/weis/darklaf/icons/light/navigation/arrowRightSelected.svg b/src/main/resources/com/weis/darklaf/icons/light/navigation/arrowRightSelected.svg
new file mode 100644
index 00000000..b84a1050
--- /dev/null
+++ b/src/main/resources/com/weis/darklaf/icons/light/navigation/arrowRightSelected.svg
@@ -0,0 +1,4 @@
+
+
+
diff --git a/src/main/resources/com/weis/darklaf/icons/light/navigation/arrowUpHover.svg b/src/main/resources/com/weis/darklaf/icons/light/navigation/arrowUpHover.svg
new file mode 100644
index 00000000..5380b781
--- /dev/null
+++ b/src/main/resources/com/weis/darklaf/icons/light/navigation/arrowUpHover.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/main/resources/com/weis/darklaf/icons/light/navigation/arrowUpSelected.svg b/src/main/resources/com/weis/darklaf/icons/light/navigation/arrowUpSelected.svg
new file mode 100644
index 00000000..e011b0b8
--- /dev/null
+++ b/src/main/resources/com/weis/darklaf/icons/light/navigation/arrowUpSelected.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/main/resources/com/weis/darklaf/properties/platform/windows.properties b/src/main/resources/com/weis/darklaf/properties/platform/windows.properties
index 4dff0aa5..9dba80dc 100644
--- a/src/main/resources/com/weis/darklaf/properties/platform/windows.properties
+++ b/src/main/resources/com/weis/darklaf/properties/platform/windows.properties
@@ -62,3 +62,5 @@ ToolBar.borderColor = %border
#MenuBar
MenuBar.border = com.weis.darklaf.ui.menu.DarkMenuBarBorder
MenuBar.borderColor = %border
+
+FileChooser.listViewWindowsStyle = true
\ No newline at end of file
diff --git a/src/main/resources/com/weis/darklaf/properties/ui/comboBox.properties b/src/main/resources/com/weis/darklaf/properties/ui/comboBox.properties
index 1bfc7af2..c8bc85d4 100644
--- a/src/main/resources/com/weis/darklaf/properties/ui/comboBox.properties
+++ b/src/main/resources/com/weis/darklaf/properties/ui/comboBox.properties
@@ -29,3 +29,4 @@ ComboBox.focusBorderColor = %glowFocusLine
ComboBox.inactiveBackground = %widgetFillInactive
ComboBox.activeBackground = %background
ComboBox.disabledForeground = %textForegroundInactive
+ComboBox.disabledBackground = %widgetFillInactive
\ No newline at end of file
diff --git a/src/main/resources/com/weis/darklaf/properties/ui/fileChooser.properties b/src/main/resources/com/weis/darklaf/properties/ui/fileChooser.properties
index 2c6be708..7f728689 100644
--- a/src/main/resources/com/weis/darklaf/properties/ui/fileChooser.properties
+++ b/src/main/resources/com/weis/darklaf/properties/ui/fileChooser.properties
@@ -21,12 +21,28 @@
#SOFTWARE.
#
# suppress inspection "UnusedProperty" for whole file
-FileChooser.newFolderIcon = files/newFolder.svg[aware]
-FileChooser.upFolderIcon = files/upFolder.svg[aware]
-FileChooser.homeFolderIcon = files/homeFolder.svg[aware]
+FileChooserUI = com.weis.darklaf.ui.filechooser.DarkFileChooserUI
+FileChooser.newFolderIcon = files/newFolder.svg[aware]
+FileChooser.upFolderIcon = files/upFolder.svg[aware]
+FileChooser.homeFolderIcon = files/homeFolder.svg[aware]
+FileChooser.listViewBorder = com.weis.darklaf.ui.filechooser.DarkFileChooserListViewBorder
-FileView.fileIcon = files/general.svg[aware]
-FileView.directoryIcon = files/folder.svg[aware]
-FileChooser.detailsViewIcon = menu/listFiles.svg[aware]
-FileChooser.listViewIcon = menu/groupBy.svg[aware]
-FileView.computerIcon = files/desktop.svg[aware]
\ No newline at end of file
+FileChooser.detailsViewIcon = menu/listFiles.svg[aware]
+FileChooser.listViewIcon = menu/groupBy.svg[aware]
+
+FileChooser.borderColor = %borderSecondary
+FileChooser.rowHeight = 20
+FileChooser.minEditDelay = 200
+FileChooser.maxEditDelay = 600
+FileView.fullRowSelection = true
+FileChooser.listViewWindowsStyle = false
+FileChooser.fileSizeKiloBytes = {0} kb
+FileChooser.fileSizeMegaBytes = {0} mb
+FileChooser.fileSizeGigaBytes = {0} gb
+FileChooser.readOnly = false
+
+FileView.fileIcon = files/general.svg[aware]
+FileView.directoryIcon = files/folder.svg[aware]
+FileView.computerIcon = files/desktop.svg[aware]
+FileView.floppyDriveIcon = menu/save.svg[aware]
+FileView.hardDriveIcon = files/drive.svg[aware]
\ No newline at end of file
diff --git a/src/main/resources/com/weis/darklaf/properties/ui/list.properties b/src/main/resources/com/weis/darklaf/properties/ui/list.properties
index 3c08d425..e4b06a37 100644
--- a/src/main/resources/com/weis/darklaf/properties/ui/list.properties
+++ b/src/main/resources/com/weis/darklaf/properties/ui/list.properties
@@ -22,9 +22,12 @@
#
# suppress inspection "UnusedProperty" for whole file
ListUI = com.weis.darklaf.ui.list.DarkListUI
+List.cellRenderer = com.weis.darklaf.ui.list.DarkListCellRenderer
List.border = null
List.background = %background
-List.focusSelectedCellHighlightBorder = com.weis.darklaf.ui.list.DarkListCellBorder
+List.focusSelectedCellHighlightBorder = com.weis.darklaf.ui.list.DarkListCellFocusBorder
List.focusCellHighlightBorder = com.weis.darklaf.ui.list.DarkListCellBorder
List.cellNoFocusBorder = com.weis.darklaf.ui.list.DarkListCellBorder
List.dropLineColor = %dropForeground
+List.selectionBackground = %highlightFillFocus
+List.focusBorderColor = %borderFocus
diff --git a/src/main/resources/com/weis/darklaf/properties/ui/menu.properties b/src/main/resources/com/weis/darklaf/properties/ui/menu.properties
index e05b2795..33da1f89 100644
--- a/src/main/resources/com/weis/darklaf/properties/ui/menu.properties
+++ b/src/main/resources/com/weis/darklaf/properties/ui/menu.properties
@@ -27,6 +27,9 @@ Menu.border = com.weis.darklaf.ui.menu.DarkMenuItemBorde
Menu.selectionBackground = %highlightFillFocus
Menu.acceleratorForeground = %acceleratorForeground
Menu.acceleratorSelectionForeground = %acceleratorForeground
+Menu.submenuPopupOffsetX = -4
+Menu.submenuPopupOffsetY = -2
#Icons
-Menu.arrowIcon = navigation/arrowRight.svg[aware]
\ No newline at end of file
+Menu.arrowIcon = navigation/arrowRight.svg[aware]
+MenuItem.arrowHover.icon = navigation/arrowRightHover.svg[aware]
\ No newline at end of file
diff --git a/src/main/resources/com/weis/darklaf/properties/ui/table.properties b/src/main/resources/com/weis/darklaf/properties/ui/table.properties
index dbbc9051..ec02fe0e 100644
--- a/src/main/resources/com/weis/darklaf/properties/ui/table.properties
+++ b/src/main/resources/com/weis/darklaf/properties/ui/table.properties
@@ -37,10 +37,13 @@ Table.cellEditorBorder = com.weis.darklaf.ui.table.DarkTableCell
Table.scrollPaneBorder = com.weis.darklaf.ui.table.DarkTableBorder
Table.background = %background
Table.focusBorderColor = %borderFocus
+Table.focusRowBorderColor = %borderFocus
Table.gridColor = %gridLine
Table.dropLineColor = %dropForeground
Table.dropLineShortColor = %dropForeground
Table.focusSelectionBackground = %highlightFillFocus
+Table.focusCellBackground = %background
+Table.focusCellForeground = %textForeground
Table.selectionNoFocusBackground = %highlightFill
Table.selectionBackground = %highlightFillFocus
@@ -49,7 +52,8 @@ Table.alternateRowBackground = %backgroundAlternative
Table.renderBooleanAsCheckBox = true
Table.booleanRenderType = checkBox
+Table.rowHeight = 22
#Icons
-Table.ascendingSortIcon = menu/upDown.svg[aware]
-Table.descendingSortIcon = menu/upDown.svg[aware]
+Table.ascendingSortIcon = menu/up.svg[aware](8,16)
+Table.descendingSortIcon = menu/down.svg[aware](8,16)
diff --git a/src/main/resources/com/weis/darklaf/properties/ui/toggleButton.properties b/src/main/resources/com/weis/darklaf/properties/ui/toggleButton.properties
index d6471d7f..b7ec0b1f 100644
--- a/src/main/resources/com/weis/darklaf/properties/ui/toggleButton.properties
+++ b/src/main/resources/com/weis/darklaf/properties/ui/toggleButton.properties
@@ -27,4 +27,7 @@ ToggleButton.sliderBorderColor = %widgetBorder
ToggleButton.disabledSliderBorderColor = %widgetBorderInactive
ToggleButton.focusedSliderBorderColor = %glowFocusLine
ToggleButton.sliderColor = %controlFill
-ToggleButton.disabledSliderColor = %controlFillDisabled
\ No newline at end of file
+ToggleButton.disabledSliderColor = %controlFillDisabled
+
+ToggleButton.inactiveFillColor = %widgetFill
+ToggleButton.activeFillColor = %backgroundHoverSecondary
\ No newline at end of file
diff --git a/src/main/resources/com/weis/darklaf/properties/ui/tree.properties b/src/main/resources/com/weis/darklaf/properties/ui/tree.properties
index 97799120..b979d091 100644
--- a/src/main/resources/com/weis/darklaf/properties/ui/tree.properties
+++ b/src/main/resources/com/weis/darklaf/properties/ui/tree.properties
@@ -52,12 +52,12 @@ Tree.expandedIcon = navigation/arrowDown.svg[aware]
Tree.closedIcon = files/folder.svg[aware]
Tree.openIcon = files/folder.svg[aware]
Tree.leafIcon = files/general.svg[aware]
-Tree.collapsed.selected.focused.icon = navigation/arrowRight.svg[aware]
+Tree.collapsed.selected.focused.icon = navigation/arrowRightSelected.svg[aware]
Tree.collapsed.selected.unfocused.icon = navigation/arrowRight.svg[aware]
Tree.collapsed.unselected.focused.icon = navigation/arrowRight.svg[aware]
Tree.collapsed.unselected.unfocused.icon = navigation/arrowRight.svg[aware]
-Tree.expanded.selected.focused.icon = navigation/arrowDown.svg[aware]
+Tree.expanded.selected.focused.icon = navigation/arrowDownSelected.svg[aware]
Tree.expanded.selected.unfocused.icon = navigation/arrowDown.svg[aware]
Tree.expanded.unselected.focused.icon = navigation/arrowDown.svg[aware]
Tree.expanded.unselected.unfocused.icon = navigation/arrowDown.svg[aware]
\ No newline at end of file
diff --git a/src/main/resources/library/x64/jniplatform.dll b/src/main/resources/library/x64/jniplatform.dll
index 1be11206..622a1b86 100644
Binary files a/src/main/resources/library/x64/jniplatform.dll and b/src/main/resources/library/x64/jniplatform.dll differ
diff --git a/src/main/resources/library/x86/jniplatform.dll b/src/main/resources/library/x86/jniplatform.dll
index 3d0f9351..2448068e 100644
Binary files a/src/main/resources/library/x86/jniplatform.dll and b/src/main/resources/library/x86/jniplatform.dll differ
diff --git a/src/test/java/FileChooserDemo.java b/src/test/java/FileChooserDemo.java
index 5589463f..88825da4 100644
--- a/src/test/java/FileChooserDemo.java
+++ b/src/test/java/FileChooserDemo.java
@@ -1,5 +1,4 @@
import com.weis.darklaf.LafManager;
-import com.weis.darklaf.theme.Theme;
import javax.swing.*;
@@ -9,11 +8,11 @@ public final class FileChooserDemo {
SwingUtilities.invokeLater(() -> {
LafManager.install();
var chooser = new JFileChooser(System.getProperty("user.home"));
+ chooser.setMultiSelectionEnabled(true);
var frame = new JFrame();
- frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
- frame.setSize(100, 100);
- frame.setVisible(true);
+ frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null);
+ frame.setVisible(true);
chooser.showOpenDialog(frame);
});
}
diff --git a/src/test/java/ToolTipDemo.java b/src/test/java/ToolTipDemo.java
index 1f543492..5ebcfaa9 100644
--- a/src/test/java/ToolTipDemo.java
+++ b/src/test/java/ToolTipDemo.java
@@ -1,4 +1,5 @@
import com.weis.darklaf.LafManager;
+import com.weis.darklaf.components.alignment.Alignment;
import com.weis.darklaf.components.tooltip.ToolTipContext;
import javax.swing.*;
@@ -13,7 +14,8 @@ public class ToolTipDemo {
JFrame f = new JFrame();
var p = new JPanel();
p.add(new JButton("Button with very very long text") {
- private final ToolTipContext context = new ToolTipContext(this);
+ private final ToolTipContext context = new ToolTipContext(this).setAlignment(Alignment.CENTER)
+ .setCenterAlignment(Alignment.SOUTH);
{
setToolTipText("ToolTip \n multiline \n third line's a charm");
diff --git a/src/test/java/UIDemo.java b/src/test/java/UIDemo.java
index a528ae97..5576bf6f 100644
--- a/src/test/java/UIDemo.java
+++ b/src/test/java/UIDemo.java
@@ -391,7 +391,13 @@ public final class UIDemo {
var menuBar = new JMenuBar();
var menu = new JMenu("test");
- menu.add(new JMenuItem("item"));
+ menu.add(new JMenu("submenu") {{
+ add(new JMenuItem("item1"));
+ add(new JMenuItem("item2"));
+ add(new JMenuItem("item3"));
+ add(new JMenuItem("item4"));
+ add(new JMenuItem("item5"));
+ }});
menu.addSeparator();
menu.add(new JRadioButtonMenuItem("radioButton"));
menu.add(new JCheckBoxMenuItem("checkBox"));
diff --git a/src/test/java/UIManagerDefaults.java b/src/test/java/UIManagerDefaults.java
index dfcad0b8..dad37e15 100644
--- a/src/test/java/UIManagerDefaults.java
+++ b/src/test/java/UIManagerDefaults.java
@@ -5,6 +5,7 @@
import com.weis.darklaf.DarkLafInfo;
import com.weis.darklaf.LafManager;
+import com.weis.darklaf.components.OverlayScrollPane;
import com.weis.darklaf.ui.cell.DarkCellRendererToggleButton;
import com.weis.darklaf.ui.table.DarkColorTableCellRendererEditor;
import org.jetbrains.annotations.Contract;
@@ -211,7 +212,7 @@ public class UIManagerDefaults implements ItemListener {
d.height = 350;
table.setPreferredScrollableViewportSize(d);
- return new JScrollPane(table);
+ return new OverlayScrollPane(table);
}
/*