From 131228a250c36991d43eb019a5688c0d27f02cef Mon Sep 17 00:00:00 2001 From: dev747368 <48332326+dev747368@users.noreply.github.com> Date: Wed, 8 May 2019 16:42:30 -0400 Subject: [PATCH 1/3] GT-2855 - fix list cell renderering sizing --- .../widgets/filechooser/DirectoryList.java | 67 ++++++++++--------- .../filechooser/DirectoryListModel.java | 4 ++ .../filechooser/FileListCellRenderer.java | 5 +- .../filechooser/GhidraFileChooser.java | 54 ++------------- .../widgets/list/GListCellRenderer.java | 28 ++++++++ 5 files changed, 77 insertions(+), 81 deletions(-) diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/filechooser/DirectoryList.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/filechooser/DirectoryList.java index 3230d625d7..c31518fc18 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/filechooser/DirectoryList.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/filechooser/DirectoryList.java @@ -32,9 +32,13 @@ import docking.widgets.list.GList; import ghidra.util.exception.AssertException; class DirectoryList extends GList 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,7 +56,7 @@ class DirectoryList extends GList implements GhidraFileChooserDirectoryMod private void build() { setLayoutOrientation(JList.VERTICAL_WRAP); - setCellRenderer(new FileListCellRenderer(chooser)); + setCellRenderer((cellRenderer = new FileListCellRenderer(getFont(), chooser))); addMouseListener(new MouseAdapter() { @Override @@ -281,36 +285,6 @@ class DirectoryList extends GList implements GhidraFileChooserDirectoryMod setSelectedIndices(indices); } - // overridden to account for the renderers insets, to avoid clipping - @Override - public void setFixedCellWidth(int width) { - - int fullWidth = width; - ListCellRenderer 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 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); } @@ -367,6 +341,37 @@ class DirectoryList extends GList implements GhidraFileChooserDirectoryMod } } + /** + * Resizes this list's cell dimensions based on the string widths found in the supplied + * list of files. + *

+ * 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. + */ + void recomputeListCellDimensions(List files) { + files = (files != null) ? files : model.getAllFiles(); + Dimension cellDims = + cellRenderer.computePlainTextListCellDimensions(this, files, 0, DEFAULT_ICON_SIZE); + if (cellDims.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(); + cellDims.width = + parentSize.width - (insets != null ? insets.right + insets.left : 0); + } + } + else { + cellDims.width += DEFAULT_ICON_SIZE + WIDTH_PADDING; + } + setFixedCellWidth(cellDims.width); + setFixedCellHeight(cellDims.height + HEIGHT_PADDING); + } + /*junit*/ JTextField getListEditorText() { return listEditorField; } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/filechooser/DirectoryListModel.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/filechooser/DirectoryListModel.java index 54547ca800..4f2a4907eb 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/filechooser/DirectoryListModel.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/filechooser/DirectoryListModel.java @@ -74,6 +74,10 @@ class DirectoryListModel extends AbstractListModel { return fileList.indexOf(file); } + public List getAllFiles() { + return new ArrayList<>(fileList); + } + @Override public int getSize() { return fileList.size(); diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/filechooser/FileListCellRenderer.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/filechooser/FileListCellRenderer.java index 714cec4134..d6a7eb0bae 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/filechooser/FileListCellRenderer.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/filechooser/FileListCellRenderer.java @@ -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 { 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 diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/filechooser/GhidraFileChooser.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/filechooser/GhidraFileChooser.java index c6e24750a2..73655abf08 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/filechooser/GhidraFileChooser.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/filechooser/GhidraFileChooser.java @@ -88,9 +88,6 @@ public class GhidraFileChooser extends DialogComponentProvider static final String NEW_FOLDER = "New Folder"; 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"); @@ -555,18 +552,11 @@ 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 fileList = new ArrayList<>(size); - for (int i = 0; i < size; i++) { - fileList.add(directoryListModel.getFile(i)); - } - computeListCellDimensions(fileList); + // called when the list changes because a new file is inserted (ie. create new folder action) + directoryList.recomputeListCellDimensions(null); } @Override @@ -850,42 +840,6 @@ public class GhidraFileChooser extends DialogComponentProvider return worker.isBusy(); } - private void computeListCellDimensions(List 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 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 +860,10 @@ public class GhidraFileChooser extends DialogComponentProvider } private void setDirectoryList(File directory, List 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 + directoryList.recomputeListCellDimensions(files); directoryTableModel.setFiles(files); directoryTable.scrollRectToVisible(new Rectangle(0, 0, 0, 0)); directoryListModel.setFiles(files); diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/list/GListCellRenderer.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/list/GListCellRenderer.java index ac052c9ecb..16cb5bf4fe 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/list/GListCellRenderer.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/list/GListCellRenderer.java @@ -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 extends AbstractGCellRenderer implements ListC protected void configureFont(JList list, ListModel model, int index) { setFont(defaultFont); } + + /** + * Returns the width, height necessary to display the largest element in this list. + *

+ * Useful for setting a JList's fixed cell width and height to the actual necessary size. + *

+ * 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 list, + List 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)); + } } From 463b74b60ddf50c76a2c57f7c62e22b154b8addb Mon Sep 17 00:00:00 2001 From: dev747368 <48332326+dev747368@users.noreply.github.com> Date: Wed, 8 May 2019 17:40:57 -0400 Subject: [PATCH 2/3] GT-2855 - GhidraFileChooser code improvements --- .../widgets/filechooser/DirectoryList.java | 52 +++++++++++++++---- .../widgets/filechooser/FileEditor.java | 22 +++++++- .../filechooser/GhidraFileChooser.java | 50 +++++++----------- 3 files changed, 83 insertions(+), 41 deletions(-) diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/filechooser/DirectoryList.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/filechooser/DirectoryList.java index c31518fc18..d41929e1c7 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/filechooser/DirectoryList.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/filechooser/DirectoryList.java @@ -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; @@ -57,6 +59,23 @@ class DirectoryList extends GList implements GhidraFileChooserDirectoryMod setLayoutOrientation(JList.VERTICAL_WRAP); setCellRenderer((cellRenderer = new FileListCellRenderer(getFont(), chooser))); + model.addListDataListener(new ListDataListener() { + @Override + 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 MouseAdapter() { @Override @@ -160,7 +179,15 @@ class DirectoryList extends GList 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 + } + else { + stopListEdit(); + } e.consume(); } } @@ -324,6 +351,13 @@ class DirectoryList extends GList implements GhidraFileChooserDirectoryMod return; } + String invalidFilenameMessage = + chooser.getInvalidFilenameMessage(listEditorField.getText()); + if (invalidFilenameMessage != null) { + chooser.setStatusText("Rename aborted - " + invalidFilenameMessage); + return; + } + File editedFileCopy = editedFile; int index = model.indexOfFile(editedFileCopy); if (index < 0) { @@ -332,6 +366,7 @@ class DirectoryList extends GList 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); @@ -350,26 +385,25 @@ class DirectoryList extends GList implements GhidraFileChooserDirectoryMod * @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. */ - void recomputeListCellDimensions(List files) { + private void recomputeListCellDimensions(List files) { files = (files != null) ? files : model.getAllFiles(); - Dimension cellDims = + Dimension d = cellRenderer.computePlainTextListCellDimensions(this, files, 0, DEFAULT_ICON_SIZE); - if (cellDims.width == 0 && getParent() != null) { + 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(); - cellDims.width = - parentSize.width - (insets != null ? insets.right + insets.left : 0); + d.width = parentSize.width - (insets != null ? insets.right + insets.left : 0); } } else { - cellDims.width += DEFAULT_ICON_SIZE + WIDTH_PADDING; + d.width += DEFAULT_ICON_SIZE + WIDTH_PADDING; } - setFixedCellWidth(cellDims.width); - setFixedCellHeight(cellDims.height + HEIGHT_PADDING); + setFixedCellWidth(d.width); + setFixedCellHeight(d.height + HEIGHT_PADDING); } /*junit*/ JTextField getListEditorText() { diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/filechooser/FileEditor.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/filechooser/FileEditor.java index 4968df99a9..7675176475 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/filechooser/FileEditor.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/filechooser/FileEditor.java @@ -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()); diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/filechooser/GhidraFileChooser.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/filechooser/GhidraFileChooser.java index 73655abf08..e247a1571a 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/filechooser/GhidraFileChooser.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/filechooser/GhidraFileChooser.java @@ -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,6 +87,7 @@ 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; @@ -551,24 +553,6 @@ public class GhidraFileChooser extends DialogComponentProvider private JScrollPane buildDirectoryList() { directoryListModel = new DirectoryListModel(); - - directoryListModel.addListDataListener(new ListDataListener() { - @Override - public void contentsChanged(ListDataEvent e) { - // called when the list changes because a new file is inserted (ie. create new folder action) - directoryList.recomputeListCellDimensions(null); - } - - @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"); @@ -863,7 +847,6 @@ public class GhidraFileChooser extends DialogComponentProvider // if the visible listing is still the same directory as this incoming list of files if (currentDirectory().equals(directory)) { // recompute list cell dims before causing an update to the model - directoryList.recomputeListCellDimensions(files); directoryTableModel.setFiles(files); directoryTable.scrollRectToVisible(new Rectangle(0, 0, 0, 0)); directoryListModel.setFiles(files); @@ -2130,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; } } From b01c3a848ed095e67e4729f4243bcf2429b0686c Mon Sep 17 00:00:00 2001 From: dev747368 <48332326+dev747368@users.noreply.github.com> Date: Thu, 9 May 2019 13:21:15 -0400 Subject: [PATCH 3/3] GT-2855 - code review fixes --- .../docking/widgets/filechooser/DirectoryList.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/filechooser/DirectoryList.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/filechooser/DirectoryList.java index d41929e1c7..e2a16741b4 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/filechooser/DirectoryList.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/filechooser/DirectoryList.java @@ -77,16 +77,15 @@ class DirectoryList extends GList implements GhidraFileChooserDirectoryMod } }); - addMouseListener(new MouseAdapter() { + 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(); } - }); - - addMouseListener(new GMouseListenerAdapter() { @Override public boolean shouldConsume(MouseEvent e) { @@ -183,7 +182,7 @@ class DirectoryList extends GList implements GhidraFileChooserDirectoryMod chooser.getInvalidFilenameMessage(listEditorField.getText()); if (invalidFilenameMessage != null) { chooser.setStatusText(invalidFilenameMessage); - // keep the user in the field + // keep the user in the field by not stopping the current edit } else { stopListEdit(); @@ -355,6 +354,7 @@ class DirectoryList extends GList implements GhidraFileChooserDirectoryMod chooser.getInvalidFilenameMessage(listEditorField.getText()); if (invalidFilenameMessage != null) { chooser.setStatusText("Rename aborted - " + invalidFilenameMessage); + cancelListEdit(); return; }