Merge remote-tracking branch 'origin/GT-2855_dev747368_listcellrenderer_sizing'

This commit is contained in:
Ryan Kurtz 2019-05-13 08:17:17 -04:00
commit 2108a5ed4c
6 changed files with 155 additions and 117 deletions

View file

@ -25,6 +25,8 @@ import java.util.ArrayList;
import java.util.List;
import javax.swing.*;
import javax.swing.event.ListDataEvent;
import javax.swing.event.ListDataListener;
import docking.event.mouse.GMouseListenerAdapter;
import docking.widgets.label.GDLabel;
@ -32,9 +34,13 @@ import docking.widgets.list.GList;
import ghidra.util.exception.AssertException;
class DirectoryList extends GList<File> implements GhidraFileChooserDirectoryModelIf {
private static final int DEFAULT_ICON_SIZE = 16;
private static final int WIDTH_PADDING = 14;
private static final int HEIGHT_PADDING = 5;
private GhidraFileChooser chooser;
private DirectoryListModel model;
private FileListCellRenderer cellRenderer;
private JLabel listEditorLabel;
private JTextField listEditorField;
private JPanel listEditor;
@ -52,18 +58,34 @@ class DirectoryList extends GList<File> implements GhidraFileChooserDirectoryMod
private void build() {
setLayoutOrientation(JList.VERTICAL_WRAP);
setCellRenderer(new FileListCellRenderer(chooser));
addMouseListener(new MouseAdapter() {
setCellRenderer((cellRenderer = new FileListCellRenderer(getFont(), chooser)));
model.addListDataListener(new ListDataListener() {
@Override
public void mouseClicked(MouseEvent e) {
// always end editing on a mouse click of any kind
listEditor.setVisible(false);
requestFocus();
public void contentsChanged(ListDataEvent e) {
// called when the list changes because a new file is inserted (ie. create new folder action)
recomputeListCellDimensions(null);
}
@Override
public void intervalAdded(ListDataEvent e) {
recomputeListCellDimensions(null);
}
@Override
public void intervalRemoved(ListDataEvent e) {
// don't care
}
});
addMouseListener(new GMouseListenerAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
super.mouseClicked(e);
// always end editing on a mouse click of any kind
listEditor.setVisible(false);
requestFocus();
}
@Override
public boolean shouldConsume(MouseEvent e) {
@ -156,7 +178,15 @@ class DirectoryList extends GList<File> implements GhidraFileChooserDirectoryMod
e.consume();
}
else if (e.getKeyCode() == KeyEvent.VK_ENTER) {
stopListEdit();
String invalidFilenameMessage =
chooser.getInvalidFilenameMessage(listEditorField.getText());
if (invalidFilenameMessage != null) {
chooser.setStatusText(invalidFilenameMessage);
// keep the user in the field by not stopping the current edit
}
else {
stopListEdit();
}
e.consume();
}
}
@ -281,36 +311,6 @@ class DirectoryList extends GList<File> implements GhidraFileChooserDirectoryMod
setSelectedIndices(indices);
}
// overridden to account for the renderers insets, to avoid clipping
@Override
public void setFixedCellWidth(int width) {
int fullWidth = width;
ListCellRenderer<? super File> renderer = getCellRenderer();
if (renderer instanceof JComponent) {
JComponent c = (JComponent) renderer;
Insets insets = c.getInsets();
fullWidth += insets.left + insets.right;
}
super.setFixedCellWidth(fullWidth);
}
// overridden to account for the renderers insets, to avoid clipping
@Override
public void setFixedCellHeight(int height) {
int fullHeight = height;
ListCellRenderer<? super File> renderer = getCellRenderer();
if (renderer instanceof JComponent) {
JComponent c = (JComponent) renderer;
Insets insets = c.getInsets();
fullHeight += insets.top + insets.bottom;
}
super.setFixedCellHeight(fullHeight);
}
private boolean isEditing() {
return (editedFile != null);
}
@ -350,6 +350,14 @@ class DirectoryList extends GList<File> implements GhidraFileChooserDirectoryMod
return;
}
String invalidFilenameMessage =
chooser.getInvalidFilenameMessage(listEditorField.getText());
if (invalidFilenameMessage != null) {
chooser.setStatusText("Rename aborted - " + invalidFilenameMessage);
cancelListEdit();
return;
}
File editedFileCopy = editedFile;
int index = model.indexOfFile(editedFileCopy);
if (index < 0) {
@ -358,6 +366,7 @@ class DirectoryList extends GList<File> implements GhidraFileChooserDirectoryMod
File dest = new File(editedFileCopy.getParentFile(), listEditorField.getText());
cancelListEdit();
if (chooser.getModel().renameFile(editedFileCopy, dest)) {
chooser.setStatusText("");
model.set(index, dest);
//chooser.updateFiles(chooser.getCurrentDirectory(), true);
chooser.setSelectedFileAndUpdateDisplay(dest);
@ -367,6 +376,36 @@ class DirectoryList extends GList<File> implements GhidraFileChooserDirectoryMod
}
}
/**
* Resizes this list's cell dimensions based on the string widths found in the supplied
* list of files.
* <p>
* If there there are no files, uses the JScrollPane that contains us for the cellwidth.
*
* @param files list of files to use to resize the list's fixed cell dimensions. If null, uses
* the model's current set of files.
*/
private void recomputeListCellDimensions(List<File> files) {
files = (files != null) ? files : model.getAllFiles();
Dimension d =
cellRenderer.computePlainTextListCellDimensions(this, files, 0, DEFAULT_ICON_SIZE);
if (d.width == 0 && getParent() != null) {
// special case: if there were no files to measure, use the containing JScrollPane's
// width
if (getParent().getParent() instanceof JScrollPane) {
JScrollPane parent = (JScrollPane) getParent().getParent();
Dimension parentSize = parent.getSize();
Insets insets = parent.getInsets();
d.width = parentSize.width - (insets != null ? insets.right + insets.left : 0);
}
}
else {
d.width += DEFAULT_ICON_SIZE + WIDTH_PADDING;
}
setFixedCellWidth(d.width);
setFixedCellHeight(d.height + HEIGHT_PADDING);
}
/*junit*/ JTextField getListEditorText() {
return listEditorField;
}

View file

@ -74,6 +74,10 @@ class DirectoryListModel extends AbstractListModel<File> {
return fileList.indexOf(file);
}
public List<File> getAllFiles() {
return new ArrayList<>(fileList);
}
@Override
public int getSize() {
return fileList.size();

View file

@ -63,6 +63,14 @@ class FileEditor extends AbstractCellEditor implements TableCellEditor {
directoryTable.editingCanceled(new ChangeEvent(FileEditor.this));
e.consume();
}
else if (e.getKeyCode() == KeyEvent.VK_ENTER) {
String invalidFilenameMessage =
chooser.getInvalidFilenameMessage(nameField.getText());
if (invalidFilenameMessage != null) {
chooser.setStatusText(invalidFilenameMessage);
e.consume();
}
}
}
@Override
@ -72,7 +80,14 @@ class FileEditor extends AbstractCellEditor implements TableCellEditor {
e.consume();
}
else if (e.getKeyCode() == KeyEvent.VK_ENTER) {
directoryTable.editingStopped(new ChangeEvent(FileEditor.this));
String invalidFilenameMessage =
chooser.getInvalidFilenameMessage(nameField.getText());
if (invalidFilenameMessage != null) {
chooser.setStatusText(invalidFilenameMessage);
}
else {
directoryTable.editingStopped(new ChangeEvent(FileEditor.this));
}
e.consume();
}
}
@ -145,6 +160,11 @@ class FileEditor extends AbstractCellEditor implements TableCellEditor {
}
private File getNewFile() {
String invalidFilenameMessage = chooser.getInvalidFilenameMessage(nameField.getText());
if (invalidFilenameMessage != null) {
chooser.setStatusText("Rename aborted - " + invalidFilenameMessage);
return originalFile;
}
GhidraFileChooserModel fileChooserModel = chooser.getModel();
File newFile = new GhidraFile(originalFile.getParentFile(), nameField.getText(),
fileChooserModel.getSeparator());

View file

@ -16,6 +16,7 @@
package docking.widgets.filechooser;
import java.awt.Component;
import java.awt.Font;
import java.io.File;
import javax.swing.JList;
@ -28,9 +29,11 @@ class FileListCellRenderer extends GListCellRenderer<File> {
private GhidraFileChooser chooser;
private GhidraFileChooserModel model;
public FileListCellRenderer(GhidraFileChooser chooser) {
public FileListCellRenderer(Font font, GhidraFileChooser chooser) {
super(font);
this.chooser = chooser;
this.model = chooser.getModel();
setShouldAlternateRowBackgroundColors(false);
}
@Override

View file

@ -22,22 +22,23 @@ import java.io.FileFilter;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.event.*;
import javax.swing.event.CellEditorListener;
import javax.swing.event.ChangeEvent;
import javax.swing.filechooser.FileSystemView;
import docking.*;
import docking.framework.DockingApplicationConfiguration;
import docking.widgets.*;
import docking.widgets.combobox.GComboBox;
import docking.widgets.label.GDLabel;
import docking.widgets.label.GLabel;
import docking.widgets.list.GListCellRenderer;
import ghidra.GhidraApplicationLayout;
import ghidra.framework.*;
import ghidra.framework.OperatingSystem;
import ghidra.framework.Platform;
import ghidra.framework.preferences.Preferences;
import ghidra.util.Msg;
import ghidra.util.SystemUtilities;
@ -86,11 +87,9 @@ public class GhidraFileChooser extends DialogComponentProvider
static final String DOT = ".";
static final String DOTDOT = "..";
static final String NEW_FOLDER = "New Folder";
static final Pattern INVALID_FILENAME_PATTERN = Pattern.compile("[/\\\\*?]");
private static final int PAD = 5;
private static final int DEFAULT_ICON_SIZE = 16;
private static final int HEIGHT_PADDING = 5;
private static final int WIDTH_PADDING = 14;
private static Icon refreshIcon = Icons.REFRESH_ICON;
private static Icon backIcon = ResourceManager.loadImage("images/left.png");
@ -554,31 +553,6 @@ public class GhidraFileChooser extends DialogComponentProvider
private JScrollPane buildDirectoryList() {
directoryListModel = new DirectoryListModel();
// SCR 3392 - 12/7/07
// initially added to resize the cells of the list when the new files are
// inserted into the list (like when creating a single new folder)
directoryListModel.addListDataListener(new ListDataListener() {
@Override
public void contentsChanged(ListDataEvent e) {
int size = directoryListModel.getSize();
List<File> fileList = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
fileList.add(directoryListModel.getFile(i));
}
computeListCellDimensions(fileList);
}
@Override
public void intervalAdded(ListDataEvent e) {
// don't care
}
@Override
public void intervalRemoved(ListDataEvent e) {
// don't care
}
});
directoryList = new DirectoryList(this, directoryListModel);
directoryList.setName("LIST");
@ -850,42 +824,6 @@ public class GhidraFileChooser extends DialogComponentProvider
return worker.isBusy();
}
private void computeListCellDimensions(List<File> files) {
Font font = directoryList.getFont();
FontMetrics metrics = directoryList.getFontMetrics(font);
int maxWidth = getDefaultMaxWidth(files, metrics);
directoryList.setFixedCellWidth(maxWidth + DEFAULT_ICON_SIZE + WIDTH_PADDING);
int rowHeight = Math.max(metrics.getHeight(), DEFAULT_ICON_SIZE);
directoryList.setFixedCellHeight(rowHeight + HEIGHT_PADDING);
}
private int getDefaultMaxWidth(List<File> theFiles, FontMetrics metrics) {
int maxWidth = 0;
if (theFiles.size() > 0) {
for (File file : theFiles) {
maxWidth = Math.max(maxWidth, metrics.stringWidth(getDisplayName(file)));
}
return maxWidth;
}
Dimension scrollSize = directoryScroll.getSize();
if (scrollSize.width == 0) {
return 0;
}
Border border = directoryScroll.getBorder();
if (border != null) {
Insets borderInsets = border.getBorderInsets(directoryScroll);
if (borderInsets != null) {
scrollSize.width -= (borderInsets.right + borderInsets.left);
}
}
return scrollSize.width - WIDTH_PADDING - DEFAULT_ICON_SIZE;
}
String getDisplayName(File file) {
if (file != null) {
if (GhidraFileChooser.MY_COMPUTER.equals(getCurrentDirectory())) {
@ -906,8 +844,9 @@ public class GhidraFileChooser extends DialogComponentProvider
}
private void setDirectoryList(File directory, List<File> files) {
// if the visible listing is still the same directory as this incoming list of files
if (currentDirectory().equals(directory)) {
computeListCellDimensions(files);
// recompute list cell dims before causing an update to the model
directoryTableModel.setFiles(files);
directoryTable.scrollRectToVisible(new Rectangle(0, 0, 0, 0));
directoryListModel.setFiles(files);
@ -2174,12 +2113,17 @@ public class GhidraFileChooser extends DialogComponentProvider
}
}
public static void main(String[] args) throws Exception {
GhidraApplicationLayout layout = new GhidraApplicationLayout();
Application.initializeApplication(layout, new DockingApplicationConfiguration());
GhidraFileChooser chooser = new GhidraFileChooser(null);
chooser.show();
System.exit(0);
String getInvalidFilenameMessage(String filename) {
switch (filename) {
case ".":
case "..":
return "Reserved name '" + filename + "'";
default:
Matcher m = GhidraFileChooser.INVALID_FILENAME_PATTERN.matcher(filename);
if (m.find()) {
return "Invalid characters: " + m.group();
}
}
return null;
}
}

View file

@ -16,6 +16,7 @@
package docking.widgets.list;
import java.awt.*;
import java.util.List;
import java.util.function.Function;
import javax.swing.*;
@ -121,4 +122,31 @@ public class GListCellRenderer<E> extends AbstractGCellRenderer implements ListC
protected void configureFont(JList<? extends E> list, ListModel<? extends E> model, int index) {
setFont(defaultFont);
}
/**
* Returns the width, height necessary to display the largest element in this list.
* <p>
* Useful for setting a JList's fixed cell width and height to the actual necessary size.
* <p>
* NOTE: the items and the renderer must be in plain text mode, not HTML rendering mode.
*
* @param list the JList that uses this cell renderer
* @param items the items to measure
* @param minWidth the minimum width that can be returned
* @param minHeight the minimum height that can be returned
* @return a new Dimension containing a width and height value necessary to display the largest
* element in the list
*/
public Dimension computePlainTextListCellDimensions(JList<? extends E> list,
List<E> items,
int minWidth, int minHeight) {
configureFont(list, list.getModel(), 0);
FontMetrics metrics = getFontMetrics(getFont());
int maxWidth = minWidth;
for (E item : items) {
String text = getItemText(item).toString();
maxWidth = Math.max(maxWidth, metrics.stringWidth(text));
}
return new Dimension(maxWidth, Math.max(metrics.getHeight(), minHeight));
}
}