mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2024-11-21 11:31:43 +00:00
Merge remote-tracking branch
'origin/GP-3492-dragonmacher-file-chooser-editable-field--SQUASHED' (Closes #5291, Closes #7150)
This commit is contained in:
commit
ecb9bc2893
@ -402,7 +402,7 @@ public abstract class AbstractGhidraHeadedDebuggerTest
|
||||
|
||||
/**
|
||||
* Only use this to escape from pop-up menus. Otherwise, use
|
||||
* {@link #triggerEscapeKey(Component)}.
|
||||
* {@link #triggerEscape(Component)}.
|
||||
*
|
||||
* @throws AWTException
|
||||
*/
|
||||
|
@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@ -401,7 +401,7 @@ public class DebuggerModelProviderTest extends AbstractGhidraHeadedDebuggerTest
|
||||
waitForTasks();
|
||||
|
||||
modelProvider.pathField.setText("SomeNonsenseToBeCancelled");
|
||||
triggerEscapeKey(modelProvider.pathField);
|
||||
triggerEscape(modelProvider.pathField);
|
||||
waitForSwing();
|
||||
|
||||
assertPathIsThreadsContainer();
|
||||
|
@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@ -195,14 +195,14 @@ public abstract class AbstractListingMergeManagerTest extends AbstractMergeTest
|
||||
KeyboardFocusManager.getCurrentKeyboardFocusManager().getActiveWindow();
|
||||
assertNotNull(activeWindow);
|
||||
waitForSwing();
|
||||
triggerEscapeKey(activeWindow);
|
||||
triggerEscape(activeWindow);
|
||||
}
|
||||
|
||||
void escapeWindowWithTitleContaining(String partOfTitle) {
|
||||
Window win = getWindowWithTitleContaining(partOfTitle);
|
||||
if (win != null) {
|
||||
waitForSwing();
|
||||
triggerEscapeKey(win);
|
||||
triggerEscape(win);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@ -772,7 +772,7 @@ public class FrontEndPluginActionsTest extends AbstractGhidraHeadedIntegrationTe
|
||||
private void clearText(Component c, String text) {
|
||||
int n = text.length();
|
||||
for (int i = 0; i < n; i++) {
|
||||
triggerBackspaceKey(c);
|
||||
triggerBackspace(c);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -56,6 +56,8 @@ import utilities.util.reflection.ReflectionUtilities;
|
||||
* @since Tracker Id 329
|
||||
*/
|
||||
public class KeyBindingUtils {
|
||||
private static final String NO_KEYBINDING_NAME = "none";
|
||||
|
||||
private static final String LAST_KEY_BINDING_EXPORT_DIRECTORY = "LastKeyBindingExportDirectory";
|
||||
|
||||
private static final String RELEASED = "released";
|
||||
@ -338,7 +340,7 @@ public class KeyBindingUtils {
|
||||
}
|
||||
|
||||
Object keyText = im.get(keyStroke);
|
||||
if (keyText == null) {
|
||||
if (keyText == null || keyText.equals(NO_KEYBINDING_NAME)) {
|
||||
// no binding--just pick a name
|
||||
keyText = action.getValue(Action.NAME);
|
||||
if (keyText == null) {
|
||||
@ -404,7 +406,7 @@ public class KeyBindingUtils {
|
||||
int focusCondition) {
|
||||
InputMap inputMap = component.getInputMap(focusCondition);
|
||||
if (inputMap != null) {
|
||||
inputMap.put(keyStroke, "none");
|
||||
inputMap.put(keyStroke, NO_KEYBINDING_NAME);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1503,7 +1503,7 @@ public abstract class AbstractDockingTest extends AbstractGuiTest {
|
||||
triggerKey(destination, modifiers, keyCode, keyChar);
|
||||
}
|
||||
|
||||
public static void triggerEscapeKey(Component c) {
|
||||
public static void triggerEscape(Component c) {
|
||||
// text components will not perform built-in actions if they are not focused
|
||||
if (c instanceof JTextComponent) {
|
||||
triggerFocusGained(c);
|
||||
@ -1511,7 +1511,7 @@ public abstract class AbstractDockingTest extends AbstractGuiTest {
|
||||
triggerText(c, "\033");
|
||||
}
|
||||
|
||||
public static void triggerBackspaceKey(Component c) {
|
||||
public static void triggerBackspace(Component c) {
|
||||
triggerText(c, "\010");
|
||||
}
|
||||
|
||||
|
@ -29,15 +29,19 @@ import javax.swing.*;
|
||||
import javax.swing.event.CellEditorListener;
|
||||
import javax.swing.event.ChangeEvent;
|
||||
import javax.swing.filechooser.FileSystemView;
|
||||
import javax.swing.text.DefaultFormatter;
|
||||
import javax.swing.text.DefaultFormatterFactory;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import docking.*;
|
||||
import docking.actions.KeyBindingUtils;
|
||||
import docking.widgets.*;
|
||||
import docking.widgets.combobox.GComboBox;
|
||||
import docking.widgets.label.GDLabel;
|
||||
import docking.widgets.label.GLabel;
|
||||
import docking.widgets.list.GListCellRenderer;
|
||||
import docking.widgets.textfield.GFormattedTextField;
|
||||
import generic.theme.GColor;
|
||||
import generic.theme.GIcon;
|
||||
import ghidra.framework.preferences.Preferences;
|
||||
@ -171,7 +175,7 @@ public class GhidraFileChooser extends ReusableDialogComponentProvider implement
|
||||
private FileChooserToggleButton downloadsButton;
|
||||
private FileChooserToggleButton recentButton;
|
||||
|
||||
private JTextField currentPathTextField;
|
||||
private GFormattedTextField currentPathTextField;
|
||||
private DropDownSelectionTextField<File> filenameTextField;
|
||||
private DirectoryTableModel directoryTableModel;
|
||||
private DirectoryTable directoryTable;
|
||||
@ -378,13 +382,13 @@ public class GhidraFileChooser extends ReusableDialogComponentProvider implement
|
||||
|
||||
@Override
|
||||
public void editingStopped(ChangeEvent e) {
|
||||
// the user has cancelled editing in the text field (i.e., they pressed ESCAPE)
|
||||
// the user has cancelled editing in the text field (i.e., they pressed ENTER)
|
||||
enterCallback();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void editingCanceled(ChangeEvent e) {
|
||||
// the user has committed editing from the text field (i.e, they pressed ENTER)
|
||||
// the user has committed editing from the text field (i.e, they pressed ESCAPE)
|
||||
escapeCallback();
|
||||
}
|
||||
});
|
||||
@ -460,14 +464,106 @@ public class GhidraFileChooser extends ReusableDialogComponentProvider implement
|
||||
gbc.gridx = afterPathLabel;
|
||||
gbc.fill = GridBagConstraints.HORIZONTAL;
|
||||
gbc.weightx = 1.0;
|
||||
currentPathTextField = new JTextField();
|
||||
currentPathTextField.setName("Path");
|
||||
currentPathTextField.setEditable(false);
|
||||
currentPathTextField = buildPathTextField();
|
||||
headerPanel.add(currentPathTextField, gbc);
|
||||
|
||||
return headerPanel;
|
||||
}
|
||||
|
||||
private GFormattedTextField buildPathTextField() {
|
||||
DefaultFormatter formatter = new DefaultFormatter();
|
||||
formatter.setOverwriteMode(false);
|
||||
DefaultFormatterFactory factory = new DefaultFormatterFactory(formatter);
|
||||
GFormattedTextField textField = new GFormattedTextField(factory, "") {
|
||||
@Override
|
||||
public void setText(String t) {
|
||||
super.setText(t);
|
||||
setDefaultValue(t);
|
||||
}
|
||||
};
|
||||
textField.setName("Path");
|
||||
|
||||
textField.addFocusListener(new FocusAdapter() {
|
||||
@Override
|
||||
public void focusGained(FocusEvent e) {
|
||||
lastInputFocus = textField;
|
||||
}
|
||||
});
|
||||
|
||||
DockingUtils.installUndoRedo(textField);
|
||||
|
||||
// have the Escape key clear any edits to the field
|
||||
KeyStroke escapeKs = KeyBindingUtils.parseKeyStroke("Escape");
|
||||
Action escapeAction = new AbstractAction("Reset Path") {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
|
||||
if (textField.isChanged()) {
|
||||
textField.reset();
|
||||
}
|
||||
else {
|
||||
// When not edited, pass the event up to the chooser so the behavior works as
|
||||
// it does elsewhere in the dialog.
|
||||
escapeCallback();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// remove the table's escape key binding and then add our own
|
||||
KeyBindingUtils.clearKeyBinding(textField, escapeKs);
|
||||
KeyBindingUtils.registerAction(textField, escapeKs, escapeAction,
|
||||
JComponent.WHEN_FOCUSED);
|
||||
|
||||
// update Enter to allow the user to pick the selected language
|
||||
KeyStroke enterKs = KeyBindingUtils.parseKeyStroke("Enter");
|
||||
Action enterAction = new AbstractAction("Choose File") {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
|
||||
if (!textField.isChanged()) {
|
||||
// When not edited, pass the event up to the chooser so the behavior works as
|
||||
// it does elsewhere in the dialog.
|
||||
enterCallback();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!textField.isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
String text = textField.getText();
|
||||
File f = new File(text);
|
||||
if (f.isFile()) {
|
||||
setSelectedFile(f);
|
||||
}
|
||||
else {
|
||||
updateDirOnly(f, true);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// remove the table's enter key binding and then add our own
|
||||
KeyBindingUtils.clearKeyBinding(textField, enterKs);
|
||||
KeyBindingUtils.registerAction(textField, enterKs, enterAction,
|
||||
JComponent.WHEN_FOCUSED);
|
||||
|
||||
// an input verifier that returns true if the path is an existing file or directory
|
||||
InputVerifier inputVerifier = new InputVerifier() {
|
||||
@Override
|
||||
public boolean verify(JComponent input) {
|
||||
String text = textField.getText();
|
||||
File f = new File(text);
|
||||
if (isSpecialDirectory(f)) {
|
||||
return true;
|
||||
}
|
||||
return f.isFile() || f.isDirectory();
|
||||
}
|
||||
};
|
||||
textField.setInputVerifier(inputVerifier);
|
||||
|
||||
return textField;
|
||||
}
|
||||
|
||||
private void buildWaitPanel() {
|
||||
waitPanel = new JPanel(new BorderLayout());
|
||||
waitPanel.setBorder(BorderFactory.createLoweredBevelBorder());
|
||||
@ -753,7 +849,8 @@ public class GhidraFileChooser extends ReusableDialogComponentProvider implement
|
||||
}
|
||||
|
||||
private File currentDirectory() {
|
||||
String path = currentPathTextField.getText();
|
||||
// The default text should always be valid, regardless of user edits
|
||||
String path = currentPathTextField.getDefaultText();
|
||||
if (path.length() == 0) {
|
||||
return null;
|
||||
}
|
||||
|
@ -97,6 +97,15 @@ public class GFormattedTextField extends JFormattedTextField {
|
||||
update();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default text. This is useful to know what the original text is after the user
|
||||
* has edited the text.
|
||||
* @return the default text
|
||||
*/
|
||||
public String getDefaultText() {
|
||||
return defaultText;
|
||||
}
|
||||
|
||||
public void disableFocusEventProcessing() {
|
||||
ignoreFocusEditChanges = true;
|
||||
}
|
||||
@ -165,6 +174,30 @@ public class GFormattedTextField extends JFormattedTextField {
|
||||
update();
|
||||
}
|
||||
|
||||
/**
|
||||
* Restores this field to its default text.
|
||||
*/
|
||||
public void reset() {
|
||||
setText(defaultText);
|
||||
update();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the contents of this field do not match the default.
|
||||
* @return true if the contents of this field do not match the default.
|
||||
*/
|
||||
public boolean isChanged() {
|
||||
return getTextEntryStatus() != Status.UNCHANGED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the contents of this field are invalid, as determined by the InputValidator.
|
||||
* @return true if the contents of this field are invalid, as determined by the InputValidator.
|
||||
*/
|
||||
public boolean isInvalid() {
|
||||
return getTextEntryStatus() == Status.INVALID;
|
||||
}
|
||||
|
||||
public void editingFinished() {
|
||||
update();
|
||||
}
|
||||
@ -196,7 +229,6 @@ public class GFormattedTextField extends JFormattedTextField {
|
||||
}
|
||||
|
||||
private void update() {
|
||||
|
||||
updateStatus();
|
||||
if (isError) {
|
||||
setForeground(Colors.FOREGROUND);
|
||||
@ -231,5 +263,4 @@ public class GFormattedTextField extends JFormattedTextField {
|
||||
textEntryStatusChanged(currentStatus);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@ -50,6 +50,8 @@ import docking.test.AbstractDockingTest;
|
||||
import docking.widgets.DropDownSelectionTextField;
|
||||
import docking.widgets.SpyDropDownWindowVisibilityListener;
|
||||
import docking.widgets.table.*;
|
||||
import docking.widgets.textfield.GFormattedTextField;
|
||||
import docking.widgets.textfield.GFormattedTextField.Status;
|
||||
import generic.concurrent.ConcurrentQ;
|
||||
import ghidra.framework.OperatingSystem;
|
||||
import ghidra.framework.Platform;
|
||||
@ -367,7 +369,7 @@ public class GhidraFileChooserTest extends AbstractDockingTest {
|
||||
|
||||
// a string long enough to make the pick unique, in case there are similarly named files
|
||||
String filenameText = prefix.substring(0, 13);
|
||||
typeTextForTextField(filenameText);
|
||||
typeTextForFilenameTextField(filenameText);
|
||||
triggerEnter(getFilenameTextField());
|
||||
|
||||
// the following code should be put back if we move to the Enter key press simply closing the
|
||||
@ -802,7 +804,7 @@ public class GhidraFileChooserTest extends AbstractDockingTest {
|
||||
|
||||
setFilenameFieldText("");// clear any text so that we can trigger the matching window
|
||||
String desktopText = "test";
|
||||
typeTextForTextField(desktopText);
|
||||
typeTextForFilenameTextField(desktopText);
|
||||
|
||||
assertDropDownWindowIsShowing(true);
|
||||
|
||||
@ -1976,6 +1978,70 @@ public class GhidraFileChooserTest extends AbstractDockingTest {
|
||||
assertChooserPreference(key, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEditPath_Directory() throws Exception {
|
||||
|
||||
TestFiles files = createMixedDirectory();
|
||||
File dir = files.randomDir();
|
||||
setPathFieldText(dir.getAbsolutePath());
|
||||
|
||||
triggerEnter(getPathTextField());
|
||||
waitForChooser();
|
||||
|
||||
File currentDir = getCurrentDirectory();
|
||||
assertEquals(dir, currentDir);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEditPath_File() throws Exception {
|
||||
TestFiles files = createMixedDirectory();
|
||||
File file = files.randomFile();
|
||||
setPathFieldText(file.getAbsolutePath());
|
||||
|
||||
triggerEnter(getPathTextField());
|
||||
waitForChooser();
|
||||
|
||||
File currentDir = getCurrentDirectory();
|
||||
File parent = file.getParentFile();
|
||||
assertEquals(parent, currentDir);
|
||||
|
||||
File selectedFile = getSelectedFile();
|
||||
assertEquals(file, selectedFile);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEditPath_InvalidPath_Enter() throws Exception {
|
||||
|
||||
File startDir = getCurrentDirectory();
|
||||
|
||||
setPathFieldText("/some/fake/path");
|
||||
triggerEnter(getPathTextField());
|
||||
waitForChooser();
|
||||
|
||||
assertPathFieldIsInvalid();
|
||||
|
||||
// no change
|
||||
File currentDir = getCurrentDirectory();
|
||||
assertEquals(startDir, currentDir);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEditPath_InvalidPath_Escape() throws Exception {
|
||||
|
||||
File startDir = getCurrentDirectory();
|
||||
|
||||
setPathFieldText("/some/fake/path");
|
||||
assertPathFieldIsInvalid();
|
||||
|
||||
triggerEscape(getPathTextField());
|
||||
waitForChooser();
|
||||
assertPathFieldIsUnchanged();
|
||||
|
||||
// no change
|
||||
File currentDir = getCurrentDirectory();
|
||||
assertEquals(startDir, currentDir);
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
// Private Methods
|
||||
//==================================================================================================
|
||||
@ -2348,7 +2414,7 @@ public class GhidraFileChooserTest extends AbstractDockingTest {
|
||||
}
|
||||
}
|
||||
|
||||
private void typeTextForTextField(String text) {
|
||||
private void typeTextForFilenameTextField(String text) {
|
||||
JTextField textField = getFilenameTextField();
|
||||
triggerText(textField, text);
|
||||
waitForSwing();
|
||||
@ -2375,11 +2441,20 @@ public class GhidraFileChooserTest extends AbstractDockingTest {
|
||||
}
|
||||
}
|
||||
|
||||
private void setFilenameFieldText(final String text) {
|
||||
final JTextField textField = getFilenameTextField();
|
||||
private void setFilenameFieldText(String text) {
|
||||
JTextField textField = getFilenameTextField();
|
||||
runSwing(() -> textField.requestFocusInWindow());
|
||||
runSwing(() -> textField.setText(text));
|
||||
}
|
||||
|
||||
private void setPathFieldText(String text) {
|
||||
GFormattedTextField textField = getPathTextField();
|
||||
runSwing(() -> textField.requestFocusInWindow());
|
||||
|
||||
runSwing(() -> textField.setText(text));
|
||||
// note: we cannot call textField.setText() here, as that is overridden to make the new
|
||||
// text the default value, which affects our test for textField.isChanged().
|
||||
triggerText(textField, text);
|
||||
waitForSwing();
|
||||
}
|
||||
|
||||
private String getFilenameFieldText() {
|
||||
@ -2393,6 +2468,10 @@ public class GhidraFileChooserTest extends AbstractDockingTest {
|
||||
return textField;
|
||||
}
|
||||
|
||||
private GFormattedTextField getPathTextField() {
|
||||
return (GFormattedTextField) findComponentByName(chooser, "Path");
|
||||
}
|
||||
|
||||
private void show() throws Exception {
|
||||
show(true);
|
||||
}
|
||||
@ -2743,6 +2822,18 @@ public class GhidraFileChooserTest extends AbstractDockingTest {
|
||||
fail("File chooser does not in its list have file: " + expected);
|
||||
}
|
||||
|
||||
private void assertPathFieldIsInvalid() {
|
||||
GFormattedTextField textField = getPathTextField();
|
||||
Status status = runSwing(() -> textField.getTextEntryStatus());
|
||||
assertEquals(Status.INVALID, status);
|
||||
}
|
||||
|
||||
private void assertPathFieldIsUnchanged() {
|
||||
GFormattedTextField textField = getPathTextField();
|
||||
Status status = runSwing(() -> textField.getTextEntryStatus());
|
||||
assertEquals(Status.UNCHANGED, status);
|
||||
}
|
||||
|
||||
private void assertChooserHidden() {
|
||||
assertFalse("The chooser is showing; it should be closed",
|
||||
runSwing(() -> chooser.isShowing()));
|
||||
|
@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@ -99,7 +99,7 @@ public class IntegerTextFieldTest extends AbstractDockingTest {
|
||||
assertTrue(!field.isHexMode());
|
||||
triggerText(textField, "x");
|
||||
assertTrue(field.isHexMode());
|
||||
triggerBackspaceKey(textField);
|
||||
triggerBackspace(textField);
|
||||
assertTrue(!field.isHexMode());
|
||||
}
|
||||
|
||||
@ -200,7 +200,7 @@ public class IntegerTextFieldTest extends AbstractDockingTest {
|
||||
assertEquals(12, listener.values.get(1));
|
||||
assertEquals(123, listener.values.get(2));
|
||||
|
||||
triggerBackspaceKey(textField);
|
||||
triggerBackspace(textField);
|
||||
assertEquals(12, listener.values.get(3));
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user