diff --git a/Ghidra/Features/Base/src/main/help/help/topics/ConsolePlugin/console.html b/Ghidra/Features/Base/src/main/help/help/topics/ConsolePlugin/console.html index d6150e20b8..7480eb9e09 100644 --- a/Ghidra/Features/Base/src/main/help/help/topics/ConsolePlugin/console.html +++ b/Ghidra/Features/Base/src/main/help/help/topics/ConsolePlugin/console.html @@ -53,6 +53,5 @@

Provided by: ConsolePlugin

-

Related Topics:

diff --git a/Ghidra/Features/Base/src/test.slow/java/docking/widgets/table/GTableTest.java b/Ghidra/Features/Base/src/test.slow/java/docking/widgets/table/GTableTest.java index 79e4213510..b8112d6644 100644 --- a/Ghidra/Features/Base/src/test.slow/java/docking/widgets/table/GTableTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/docking/widgets/table/GTableTest.java @@ -15,7 +15,7 @@ */ package docking.widgets.table; -import static org.junit.Assert.assertEquals; +import static org.junit.Assert.*; import java.awt.BorderLayout; @@ -36,7 +36,6 @@ public class GTableTest extends AbstractGhidraHeadedIntegrationTest { public void setUp() throws Exception { model = new TestDataModel(); table = new GhidraTable(model); - table.setAutoLookupColumn(4); frame = new JFrame("Ghidra Table Test"); frame.getContentPane().setLayout(new BorderLayout()); @@ -52,6 +51,9 @@ public class GTableTest extends AbstractGhidraHeadedIntegrationTest { @Test public void testAutoLookup() throws Exception { + + table.setAutoLookupColumn(4); + setSelectedRow(table, 0); triggerText(table, "a"); @@ -72,6 +74,44 @@ public class GTableTest extends AbstractGhidraHeadedIntegrationTest { assertEquals(11, table.getSelectedRow()); } + @Test + public void testSetActionsEnabled() throws Exception { + + table.setAutoLookupColumn(4); + assertFalse(table.areActionsEnabled()); + setSelectedRow(table, 0); + + triggerText(table, "a"); + assertEquals("Auto-lookup failed to change the table row", 11, table.getSelectedRow()); + + // this will disable 'auto lookup' + table.setActionsEnabled(true); + setSelectedRow(table, 0); + + triggerText(table, "a"); + assertEquals("Auto-lookup should be disabled when actions are enabled", 0, + table.getSelectedRow()); + + table.setActionsEnabled(false); + setSelectedRow(table, 0); + + triggerText(table, "a"); + assertEquals("Auto-lookup failed to change the table row", 11, table.getSelectedRow()); + + table.setActionsEnabled(true); + setSelectedRow(table, 0); + + triggerText(table, "a"); + assertEquals("Auto-lookup should be disabled when actions are enabled", 0, + table.getSelectedRow()); + + table.setAutoLookupColumn(4); + setSelectedRow(table, 0); + + triggerText(table, "a"); + assertEquals("Auto-lookup failed to change the table row", 11, table.getSelectedRow()); + } + private void timeout() throws InterruptedException { Thread.sleep(GTable.KEY_TIMEOUT * 2); } diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/codebrowser/AbstractCodeBrowserNavigationTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/codebrowser/AbstractCodeBrowserNavigationTest.java index 71b3f10de7..214a0b3693 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/codebrowser/AbstractCodeBrowserNavigationTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/codebrowser/AbstractCodeBrowserNavigationTest.java @@ -59,7 +59,7 @@ public class AbstractCodeBrowserNavigationTest extends AbstractGhidraHeadedInteg addrFactory = program.getAddressFactory(); NextPrevAddressPlugin np = env.getPlugin(NextPrevAddressPlugin.class); - prev = getAction(np, "Previous in History Buffer"); + prev = getAction(np, "Previous Location in History"); clearHistory = getAction(np, "Clear History Buffer"); cb = env.getPlugin(CodeBrowserPlugin.class); diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/codebrowser/CodeBrowserNavigationSegmentedAddressTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/codebrowser/CodeBrowserNavigationSegmentedAddressTest.java index 644a9c0e18..be6d529f4b 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/codebrowser/CodeBrowserNavigationSegmentedAddressTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/codebrowser/CodeBrowserNavigationSegmentedAddressTest.java @@ -65,7 +65,7 @@ public class CodeBrowserNavigationSegmentedAddressTest extends AbstractGhidraHea tool.addPlugin(LocationReferencesPlugin.class.getName()); tool.addPlugin(MarkerManagerPlugin.class.getName()); NextPrevAddressPlugin np = env.getPlugin(NextPrevAddressPlugin.class); - prev = getAction(np, "Previous in History Buffer"); + prev = getAction(np, "Previous Location in History"); clearHistory = getAction(np, "Clear History Buffer"); cb = env.getPlugin(CodeBrowserPlugin.class); nextFunction = getAction(cb, "Go to next function"); diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/codebrowser/CodeBrowserOptionsTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/codebrowser/CodeBrowserOptionsTest.java index 31506be5f3..357612a8be 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/codebrowser/CodeBrowserOptionsTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/codebrowser/CodeBrowserOptionsTest.java @@ -184,7 +184,6 @@ public class CodeBrowserOptionsTest extends AbstractGhidraHeadedIntegrationTest assertEquals("Cursor", groups[idx++]); assertEquals("Cursor Text Highlight", groups[idx++]); assertEquals("EOL Comments Field", groups[idx++]); - assertEquals("Field Name", groups[idx++]); assertEquals("Format Code", groups[idx++]); assertEquals("Function Pointers", groups[idx++]); assertEquals("Function Signature Field", groups[idx++]); @@ -193,9 +192,9 @@ public class CodeBrowserOptionsTest extends AbstractGhidraHeadedIntegrationTest assertEquals("Mouse", groups[idx++]); assertEquals("Operands Field", groups[idx++]); assertEquals("Pcode Field", groups[idx++]); - assertEquals("Plate Comment", groups[idx++]); - assertEquals("Post Comment", groups[idx++]); - assertEquals("Pre-Comment", groups[idx++]); + assertEquals("Plate Comments Field", groups[idx++]); + assertEquals("Post-comments Field", groups[idx++]); + assertEquals("Pre-comments Field", groups[idx++]); assertEquals("Register Field", groups[idx++]); assertEquals("Selection Colors", groups[idx++]); assertEquals("XREFs Field", groups[idx++]); diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/codebrowser/CodeBrowserTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/codebrowser/CodeBrowserTest.java index 7458d21c43..209885a8d6 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/codebrowser/CodeBrowserTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/codebrowser/CodeBrowserTest.java @@ -76,7 +76,7 @@ public class CodeBrowserTest extends AbstractGhidraHeadedIntegrationTest { pm.openProgram(program.getDomainFile()); NextPrevAddressPlugin np = env.getPlugin(NextPrevAddressPlugin.class); - getAction(np, "Previous in History Buffer"); + getAction(np, "Previous Location in History"); getAction(np, "Clear History Buffer"); cb = env.getPlugin(CodeBrowserPlugin.class); fp = cb.getFieldPanel(); diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/navigation/GoToPluginTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/navigation/GoToPluginTest.java index 1002e1a333..52d02a27fa 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/navigation/GoToPluginTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/navigation/GoToPluginTest.java @@ -603,8 +603,8 @@ public class GoToPluginTest extends AbstractGhidraHeadedIntegrationTest { public void testNextPrevious() throws Exception { tool.addPlugin(NextPrevAddressPlugin.class.getName()); NextPrevAddressPlugin np = env.getPlugin(NextPrevAddressPlugin.class); - DockingActionIf next = getAction(np, "Next in History Buffer"); - DockingActionIf prev = getAction(np, "Previous in History Buffer"); + DockingActionIf next = getAction(np, "Next Location in History"); + DockingActionIf prev = getAction(np, "Previous Location in History"); DockingActionIf clear = getAction(np, "Clear History Buffer"); assertTrue(!clear.isEnabledForContext(provider.getActionContext(null))); assertTrue(!next.isEnabledForContext(provider.getActionContext(null))); @@ -765,7 +765,7 @@ public class GoToPluginTest extends AbstractGhidraHeadedIntegrationTest { setOptionToAllowNavigationToOtherOpenPrograms(); performOkCallback(); - assertTrue("Expected goto to succeed and dialog to be gone", !dialog.isShowing()); + assertFalse("Expected goto to succeed and dialog to be gone", dialog.isShowing()); } diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/navigation/NavigationHistoryPluginTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/navigation/NavigationHistoryPluginTest.java index 426f4a65ae..b02fb316a3 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/navigation/NavigationHistoryPluginTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/navigation/NavigationHistoryPluginTest.java @@ -75,8 +75,8 @@ public class NavigationHistoryPluginTest extends AbstractGhidraHeadedIntegration cb = env.getPlugin(CodeBrowserPlugin.class); goToService = tool.getService(GoToService.class); navigatable = goToService.getDefaultNavigatable(); - prevAction = getAction(nextPrevPlugin, "Previous in History Buffer"); - nextAction = getAction(nextPrevPlugin, "Next in History Buffer"); + prevAction = getAction(nextPrevPlugin, "Previous Location in History"); + nextAction = getAction(nextPrevPlugin, "Next Location in History"); ProgramManagerPlugin pmp = env.getPlugin(ProgramManagerPlugin.class); undoAction = getAction(pmp, "Undo"); redoAction = getAction(pmp, "Redo"); diff --git a/Ghidra/Features/FunctionGraph/src/test/java/ghidra/app/plugin/core/functiongraph/AbstractFunctionGraphTest.java b/Ghidra/Features/FunctionGraph/src/test/java/ghidra/app/plugin/core/functiongraph/AbstractFunctionGraphTest.java index ffb91b472f..2ef6e8c676 100644 --- a/Ghidra/Features/FunctionGraph/src/test/java/ghidra/app/plugin/core/functiongraph/AbstractFunctionGraphTest.java +++ b/Ghidra/Features/FunctionGraph/src/test/java/ghidra/app/plugin/core/functiongraph/AbstractFunctionGraphTest.java @@ -2291,7 +2291,7 @@ public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedInte } protected void navigateBack() { - String name = "Previous in History Buffer"; + String name = "Previous Location in History"; DockingActionIf action = getAction(tool, "NextPrevAddressPlugin", name); performAction(action, true); diff --git a/Ghidra/Features/Python/src/main/help/help/topics/Python/interpreter.html b/Ghidra/Features/Python/src/main/help/help/topics/Python/interpreter.html index a351b93db0..ddc272d65e 100644 --- a/Ghidra/Features/Python/src/main/help/help/topics/Python/interpreter.html +++ b/Ghidra/Features/Python/src/main/help/help/topics/Python/interpreter.html @@ -165,6 +165,5 @@

Provided by: PythonPlugin

-

Related Topics:

diff --git a/Ghidra/Framework/Docking/src/main/java/docking/actions/KeyBindingUtils.java b/Ghidra/Framework/Docking/src/main/java/docking/actions/KeyBindingUtils.java index 793de40b0b..b7b48bfee9 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/actions/KeyBindingUtils.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/actions/KeyBindingUtils.java @@ -176,8 +176,14 @@ public class KeyBindingUtils { /** * Changes the given key event to the new source component and then dispatches that event. * This method is intended for clients that wish to effectively take a key event given to - * one component and give it to another component. This is seldom-used code; if you don't - * know when to use this code, then don't. + * one component and give it to another component. + * + *

This method exists to deal with the complicated nature of key event processing and + * how our (not Java's) framework processes key event bindings to trigger actions. If not + * for our special processing of action key bindings, then this method would not be + * necessary. + * + *

This is seldom-used code; if you don't know when to use this code, then don't. * * @param newSource the new target of the event * @param e the existing event @@ -190,9 +196,44 @@ public class KeyBindingUtils { KeyEvent newEvent = new KeyEvent(newSource, e.getID(), e.getWhen(), e.getModifiersEx(), e.getKeyCode(), e.getKeyChar(), e.getKeyLocation()); - e.consume(); + + /* + Unusual Code Alert! + + The KeyboardFocusManager is a complicated beast. Here we use knowledge of one such + complication to correctly route key events. If the client of this method passes + a component whose 'isShowing()' returns false, then the manager will not send the + event to that component. Almost all clients will pass fully attached/realized + components to the manager. We, however, will sometimes pass components that are not + attached; for example, when we are using said components with a renderer to perform + our own painting. In the case of non-attached components, we must call the + redispatchEvent() method ourselves. + + Why don't we just always call redispatchEvent()? Well, that + method will not pass the new cloned event we just created back through the full + key event pipeline. This means that tool-level (our Tool API, not Java) + actions will not work, as tool-level actions are handled at the beginning of the + key event pipeline, not by the components themselves. + + Also, we have here guilty knowledge that the aforementioned tool-level key processing + will check to see if the event was consumed. If consumed, then no further processing + will happen; if not consumed, then the framework will continue to process the event + passed into this method. Thus, after we send the new event, we will update the + original event to match the consumed state of our new event. This means that the + component passed to this method must, somewhere in its processing, consume the key + event we dispatch here, if they do not wish for any further processing to take place. + */ KeyboardFocusManager kfm = KeyboardFocusManager.getCurrentKeyboardFocusManager(); - kfm.dispatchEvent(newEvent); + if (newSource.isShowing()) { + kfm.dispatchEvent(newEvent); + } + else { + kfm.redispatchEvent(newSource, newEvent); + } + + if (newEvent.isConsumed()) { + e.consume(); + } } /** diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GTable.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GTable.java index a1b37ef984..174e65c954 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GTable.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GTable.java @@ -84,7 +84,7 @@ public class GTable extends JTable { private int userDefinedRowHeight; private boolean isInitialized; - private boolean allowActions; + private boolean enableActionKeyBindings; private KeyListener autoLookupListener; private long lastLookupTime; private String lookupString; @@ -386,6 +386,11 @@ public class GTable extends JTable { /** * Sets the column in which auto-lookup will be enabled. + * + *

Note: calling this method with a valid column index will disable key binding support + * of actions. See {@link #setActionsEnabled(boolean)}. Passing an invalid column index + * will disable the auto-lookup feature. + * * @param lookupColumn the column in which auto-lookup will be enabled */ public void setAutoLookupColumn(int lookupColumn) { @@ -395,7 +400,8 @@ public class GTable extends JTable { autoLookupListener = new KeyAdapter() { @Override public void keyPressed(KeyEvent e) { - if (!allowActions) { + if (enableActionKeyBindings) { + // actions will consume key bindings, so don't process them return; } @@ -428,6 +434,7 @@ public class GTable extends JTable { if (lookupColumn >= 0 && lookupColumn < getModel().getColumnCount()) { addKeyListener(autoLookupListener); + enableActionKeyBindings = false; } else { removeKeyListener(autoLookupListener); @@ -471,7 +478,22 @@ public class GTable extends JTable { * @param b true allows keyboard actions to pass up the component hierarchy. */ public void setActionsEnabled(boolean b) { - allowActions = b; + enableActionKeyBindings = b; + } + + /** + * Returns true if key strokes are used to trigger actions. + * + *

This method has a relationship with {@link #setAutoLookupColumn(int)}. If this method + * returns true, then the auto-lookup feature is disabled. If this method + * returns false, then the auto-lookup may or may not be enabled. + * + * @return true if key strokes are used to trigger actions + * @see #setActionsEnabled(boolean) + * @see #setAutoLookupColumn(int) + */ + public boolean areActionsEnabled() { + return enableActionKeyBindings; } /** diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/util/DateUtils.java b/Ghidra/Framework/Generic/src/main/java/ghidra/util/DateUtils.java index a1a7660f25..80eb07f0a1 100644 --- a/Ghidra/Framework/Generic/src/main/java/ghidra/util/DateUtils.java +++ b/Ghidra/Framework/Generic/src/main/java/ghidra/util/DateUtils.java @@ -22,6 +22,7 @@ import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; import java.time.temporal.TemporalAccessor; import java.util.*; +import java.util.function.Predicate; import ghidra.util.exception.AssertException; @@ -265,21 +266,69 @@ public class DateUtils { //@formatter:on } + /** + * Returns a date for the given numeric values + * + * @param year the year + * @param month the month; 0-based + * @param day the day of month; 1-based + * @return the date + */ public static Date getDate(int year, int month, int day) { Calendar cal = new GregorianCalendar(year, month, day); return cal.getTime(); } + /** + * Returns all days between the two dates. Returns 0 if the same date is passed for both + * parameters. The order of the dates does not matter. + * + * @param date1 the first date + * @param date2 the second date + * @return the number of days + */ public static int getDaysBetween(Date date1, Date date2) { - date1 = normalizeDate(date1); - date2 = normalizeDate(date2); + return doGetDaysBetween(date1, date2, DateUtils::anyDay); + } + + /** + * Returns the business days between the two dates. Returns 0 if the same date is + * passed for both parameters. The order of the dates does not matter. + * + * @param date1 the first date + * @param date2 the second date + * @return the number of days + */ + public static int getBusinessDaysBetween(Date date1, Date date2) { + return doGetDaysBetween(date1, date2, DateUtils::isBusinessDay); + } + + private static boolean anyDay(Calendar c) { + return true; + } + + private static boolean isBusinessDay(Calendar c) { + return !(isWeekend(c) || isHoliday(c)); + } + + private static int doGetDaysBetween(Date date1, Date date2, Predicate dayFilter) { + + Date d1 = date1; + Date d2 = date2; + if (date1.compareTo(date2) > 0) { + d1 = date2; + d2 = date1; + } + + d1 = normalizeDate(d1); + d2 = normalizeDate(d2); Calendar cal = new GregorianCalendar(); - cal.setTime(date1); + cal.setTime(d1); int days = 0; - while (cal.getTime().compareTo(date2) < 0) { + while (cal.getTime().compareTo(d2) < 0) { cal.add(Calendar.DAY_OF_MONTH, 1); - if (!isWeekend(cal) && !isHoliday(cal)) { + if (dayFilter.test(cal)) { days++; } } diff --git a/Ghidra/Framework/Generic/src/test/java/ghidra/util/DateUtilsTest.java b/Ghidra/Framework/Generic/src/test/java/ghidra/util/DateUtilsTest.java index fb2b457750..a16a67744f 100644 --- a/Ghidra/Framework/Generic/src/test/java/ghidra/util/DateUtilsTest.java +++ b/Ghidra/Framework/Generic/src/test/java/ghidra/util/DateUtilsTest.java @@ -80,4 +80,37 @@ public class DateUtilsTest { int daysBetween = DateUtils.getDaysBetween(nowDate, futureDate); assertEquals(days, daysBetween); } + + @Test + public void testGetDaysBetween_SameDay() { + + long now = System.currentTimeMillis(); + Date nowDate = new Date(now); + int daysBetween = DateUtils.getDaysBetween(nowDate, nowDate); + assertEquals(0, daysBetween); + } + + @Test + public void testGetDaysBetween_MostRecentDateFirst() { + + long now = System.currentTimeMillis(); + int days = 3; + long threeDaysOffset = days * (24 * 60 * 60 * 1000); + long future = now + threeDaysOffset; + + Date nowDate = new Date(now); + Date futureDate = new Date(future); + int daysBetween = DateUtils.getDaysBetween(futureDate, nowDate); + assertEquals(days, daysBetween); + } + + @Test + public void testGetBusinessDaysBetween() { + + int november = 10; + Date friday = DateUtils.getDate(2019, november, 22); + Date monday = DateUtils.getDate(2019, november, 25); + int daysBetween = DateUtils.getBusinessDaysBetween(friday, monday); + assertEquals(1, daysBetween); + } } diff --git a/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/app/plugin/core/clipboard/ClipboardPluginTest.java b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/app/plugin/core/clipboard/ClipboardPluginTest.java index 94d4a3b0d8..cabcaa6b2e 100644 --- a/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/app/plugin/core/clipboard/ClipboardPluginTest.java +++ b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/app/plugin/core/clipboard/ClipboardPluginTest.java @@ -684,7 +684,7 @@ public class ClipboardPluginTest extends AbstractGhidraHeadedIntegrationTest { copySpecial(codeViewerWrapper, copySpecialAction); String clipboardContents = getClipboardContents(); - String expectedBytes = "e0"; + String expectedBytes = "0e"; assertEquals(expectedBytes, clipboardContents); }