Merge remote-tracking branch 'origin/GT-2763-dragonmacher-unsort-tables'

This commit is contained in:
ghidra1 2019-05-09 16:49:26 -04:00
commit c048022308
72 changed files with 1453 additions and 1201 deletions

View file

@ -128,13 +128,11 @@ src/main/help/help/topics/CallTreePlugin/images/CallTreeWindow.png||GHIDRA||||EN
src/main/help/help/topics/CallTreePlugin/images/arrow_rotate_clockwise.png||FAMFAMFAM Icons - CC 2.5|||famfamfam silk icon set|END| src/main/help/help/topics/CallTreePlugin/images/arrow_rotate_clockwise.png||FAMFAMFAM Icons - CC 2.5|||famfamfam silk icon set|END|
src/main/help/help/topics/CallTreePlugin/images/collapse_all.png||GHIDRA||||END| src/main/help/help/topics/CallTreePlugin/images/collapse_all.png||GHIDRA||||END|
src/main/help/help/topics/CallTreePlugin/images/depth-input.png||GHIDRA||reviewed||END| src/main/help/help/topics/CallTreePlugin/images/depth-input.png||GHIDRA||reviewed||END|
src/main/help/help/topics/CallTreePlugin/images/expand_all.png||GHIDRA||||END|
src/main/help/help/topics/CallTreePlugin/images/go-home.png||Tango Icons - Public Domain|||tango icon set|END| src/main/help/help/topics/CallTreePlugin/images/go-home.png||Tango Icons - Public Domain|||tango icon set|END|
src/main/help/help/topics/CallTreePlugin/images/locationIn.gif||GHIDRA||||END| src/main/help/help/topics/CallTreePlugin/images/locationIn.gif||GHIDRA||||END|
src/main/help/help/topics/CallTreePlugin/images/locationOut.gif||GHIDRA||||END| src/main/help/help/topics/CallTreePlugin/images/locationOut.gif||GHIDRA||||END|
src/main/help/help/topics/CallTreePlugin/images/package.png||FAMFAMFAM Icons - CC 2.5|||famfamfam silk icon set|END| src/main/help/help/topics/CallTreePlugin/images/package.png||FAMFAMFAM Icons - CC 2.5|||famfamfam silk icon set|END|
src/main/help/help/topics/CallTreePlugin/images/stopNode.png||FAMFAMFAM Icons - CC 2.5|||famfamfam silk icon set|END| src/main/help/help/topics/CallTreePlugin/images/stopNode.png||FAMFAMFAM Icons - CC 2.5|||famfamfam silk icon set|END|
src/main/help/help/topics/CallTreePlugin/images/view-filter.png||Oxygen Icons - LGPL 3.0|||Oxygen icon theme (dual license; LGPL or CC-SA-3.0)|END|
src/main/help/help/topics/ClearPlugin/Clear.htm||GHIDRA||||END| src/main/help/help/topics/ClearPlugin/Clear.htm||GHIDRA||||END|
src/main/help/help/topics/ClearPlugin/images/ClearFlow.png||GHIDRA||||END| src/main/help/help/topics/ClearPlugin/images/ClearFlow.png||GHIDRA||||END|
src/main/help/help/topics/ClearPlugin/images/ClearWithOptions.png||GHIDRA||||END| src/main/help/help/topics/ClearPlugin/images/ClearWithOptions.png||GHIDRA||||END|
@ -153,7 +151,6 @@ src/main/help/help/topics/CodeBrowserPlugin/images/ClosedStructure.png||GHIDRA||
src/main/help/help/topics/CodeBrowserPlugin/images/CodeBrowser.png||GHIDRA||||END| src/main/help/help/topics/CodeBrowserPlugin/images/CodeBrowser.png||GHIDRA||||END|
src/main/help/help/topics/CodeBrowserPlugin/images/CodeBrowserColors.png||GHIDRA||||END| src/main/help/help/topics/CodeBrowserPlugin/images/CodeBrowserColors.png||GHIDRA||||END|
src/main/help/help/topics/CodeBrowserPlugin/images/CodeBrowserReferencePopup.png||GHIDRA||||END| src/main/help/help/topics/CodeBrowserPlugin/images/CodeBrowserReferencePopup.png||GHIDRA||||END|
src/main/help/help/topics/CodeBrowserPlugin/images/CodeBrowserTruncatedTextPopup.png||GHIDRA||||END|
src/main/help/help/topics/CodeBrowserPlugin/images/CodeBrowserWithFlowArrows.png||GHIDRA||||END| src/main/help/help/topics/CodeBrowserPlugin/images/CodeBrowserWithFlowArrows.png||GHIDRA||||END|
src/main/help/help/topics/CodeBrowserPlugin/images/CodeBrowserWithMarkers.png||GHIDRA||||END| src/main/help/help/topics/CodeBrowserPlugin/images/CodeBrowserWithMarkers.png||GHIDRA||||END|
src/main/help/help/topics/CodeBrowserPlugin/images/CodeBrowser_OperandHighlight.png||GHIDRA||||END| src/main/help/help/topics/CodeBrowserPlugin/images/CodeBrowser_OperandHighlight.png||GHIDRA||||END|
@ -214,7 +211,6 @@ src/main/help/help/topics/DataTypeEditors/images/disk.png||FAMFAMFAM Icons - CC
src/main/help/help/topics/DataTypeEditors/images/down.png||GHIDRA||||END| src/main/help/help/topics/DataTypeEditors/images/down.png||GHIDRA||||END|
src/main/help/help/topics/DataTypeEditors/images/edit-delete.png||Oxygen Icons - LGPL 3.0||||END| src/main/help/help/topics/DataTypeEditors/images/edit-delete.png||Oxygen Icons - LGPL 3.0||||END|
src/main/help/help/topics/DataTypeEditors/images/erase16.png||GHIDRA||||END| src/main/help/help/topics/DataTypeEditors/images/erase16.png||GHIDRA||||END|
src/main/help/help/topics/DataTypeEditors/images/pencil16.png||GHIDRA||reviewed||END|
src/main/help/help/topics/DataTypeEditors/images/up.png||GHIDRA||||END| src/main/help/help/topics/DataTypeEditors/images/up.png||GHIDRA||||END|
src/main/help/help/topics/DataTypeManagerPlugin/data_type_manager_archives.html||GHIDRA||||END| src/main/help/help/topics/DataTypeManagerPlugin/data_type_manager_archives.html||GHIDRA||||END|
src/main/help/help/topics/DataTypeManagerPlugin/data_type_manager_description.htm||GHIDRA||||END| src/main/help/help/topics/DataTypeManagerPlugin/data_type_manager_description.htm||GHIDRA||||END|
@ -224,7 +220,6 @@ src/main/help/help/topics/DataTypeManagerPlugin/images/BookShelfOpen.png||GHIDRA
src/main/help/help/topics/DataTypeManagerPlugin/images/CommitDialog.png||GHIDRA||||END| src/main/help/help/topics/DataTypeManagerPlugin/images/CommitDialog.png||GHIDRA||||END|
src/main/help/help/topics/DataTypeManagerPlugin/images/DataTypeConflict.png||GHIDRA||||END| src/main/help/help/topics/DataTypeManagerPlugin/images/DataTypeConflict.png||GHIDRA||||END|
src/main/help/help/topics/DataTypeManagerPlugin/images/DataTypeManager.png||GHIDRA||||END| src/main/help/help/topics/DataTypeManagerPlugin/images/DataTypeManager.png||GHIDRA||||END|
src/main/help/help/topics/DataTypeManagerPlugin/images/DataTypeTreeWithAssociations.png||GHIDRA||||END|
src/main/help/help/topics/DataTypeManagerPlugin/images/DisassociateDialog.png||GHIDRA||||END| src/main/help/help/topics/DataTypeManagerPlugin/images/DisassociateDialog.png||GHIDRA||||END|
src/main/help/help/topics/DataTypeManagerPlugin/images/EditPaths.png||GHIDRA||||END| src/main/help/help/topics/DataTypeManagerPlugin/images/EditPaths.png||GHIDRA||||END|
src/main/help/help/topics/DataTypeManagerPlugin/images/FavoriteDts.png||GHIDRA||||END| src/main/help/help/topics/DataTypeManagerPlugin/images/FavoriteDts.png||GHIDRA||||END|
@ -270,7 +265,6 @@ src/main/help/help/topics/DataWindowPlugin/data_window.htm||GHIDRA||||END|
src/main/help/help/topics/DataWindowPlugin/images/DataWindow.png||GHIDRA||||END| src/main/help/help/topics/DataWindowPlugin/images/DataWindow.png||GHIDRA||||END|
src/main/help/help/topics/DataWindowPlugin/images/DataWindowFilter.png||GHIDRA||||END| src/main/help/help/topics/DataWindowPlugin/images/DataWindowFilter.png||GHIDRA||||END|
src/main/help/help/topics/DataWindowPlugin/images/text_align_justify.png||FAMFAMFAM Icons - CC 2.5|||famfamfam silk icon set|END| src/main/help/help/topics/DataWindowPlugin/images/text_align_justify.png||FAMFAMFAM Icons - CC 2.5|||famfamfam silk icon set|END|
src/main/help/help/topics/DataWindowPlugin/images/view-filter.png||Oxygen Icons - LGPL 3.0|||Oxygen icon theme (dual license; LGPL or CC-SA-3.0)|END|
src/main/help/help/topics/DbViewerPlugin/DbViewer.htm||GHIDRA||reviewed||END| src/main/help/help/topics/DbViewerPlugin/DbViewer.htm||GHIDRA||reviewed||END|
src/main/help/help/topics/DbViewerPlugin/images/DatabaseViewer.png||GHIDRA||||END| src/main/help/help/topics/DbViewerPlugin/images/DatabaseViewer.png||GHIDRA||||END|
src/main/help/help/topics/DisassembledViewPlugin/DisassembledViewPlugin.htm||GHIDRA||||END| src/main/help/help/topics/DisassembledViewPlugin/DisassembledViewPlugin.htm||GHIDRA||||END|
@ -292,7 +286,6 @@ src/main/help/help/topics/EclipseIntegration/EclipseIntegration.htm||GHIDRA||||E
src/main/help/help/topics/EquatePlugin/Equates.htm||GHIDRA||||END| src/main/help/help/topics/EquatePlugin/Equates.htm||GHIDRA||||END|
src/main/help/help/topics/EquatePlugin/images/AfterApplyEnum.png||GHIDRA||||END| src/main/help/help/topics/EquatePlugin/images/AfterApplyEnum.png||GHIDRA||||END|
src/main/help/help/topics/EquatePlugin/images/ApplyEnum.png||GHIDRA||||END| src/main/help/help/topics/EquatePlugin/images/ApplyEnum.png||GHIDRA||||END|
src/main/help/help/topics/EquatePlugin/images/ApplyEnumPopup.png||GHIDRA||||END|
src/main/help/help/topics/EquatePlugin/images/BeforeApplyEnum.png||GHIDRA||||END| src/main/help/help/topics/EquatePlugin/images/BeforeApplyEnum.png||GHIDRA||||END|
src/main/help/help/topics/EquatePlugin/images/ConfirmEquateDelete.png||GHIDRA||||END| src/main/help/help/topics/EquatePlugin/images/ConfirmEquateDelete.png||GHIDRA||||END|
src/main/help/help/topics/EquatePlugin/images/EquatesTable.png||GHIDRA||||END| src/main/help/help/topics/EquatePlugin/images/EquatesTable.png||GHIDRA||||END|
@ -302,7 +295,6 @@ src/main/help/help/topics/EquatePlugin/images/SetEquate.png||GHIDRA||||END|
src/main/help/help/topics/ExporterPlugin/exporter.htm||GHIDRA||||END| src/main/help/help/topics/ExporterPlugin/exporter.htm||GHIDRA||||END|
src/main/help/help/topics/ExporterPlugin/images/Ascii_Options.png||GHIDRA||||END| src/main/help/help/topics/ExporterPlugin/images/Ascii_Options.png||GHIDRA||||END|
src/main/help/help/topics/ExporterPlugin/images/C_Options.png||GHIDRA||||END| src/main/help/help/topics/ExporterPlugin/images/C_Options.png||GHIDRA||||END|
src/main/help/help/topics/ExporterPlugin/images/Export_Data_Panel.png||GHIDRA||reviewed||END|
src/main/help/help/topics/ExporterPlugin/images/Export_Dialog.png||GHIDRA||||END| src/main/help/help/topics/ExporterPlugin/images/Export_Dialog.png||GHIDRA||||END|
src/main/help/help/topics/ExporterPlugin/images/Intel_Hex_Options.png||GHIDRA||||END| src/main/help/help/topics/ExporterPlugin/images/Intel_Hex_Options.png||GHIDRA||||END|
src/main/help/help/topics/FallThroughPlugin/Override_Fallthrough.htm||GHIDRA||||END| src/main/help/help/topics/FallThroughPlugin/Override_Fallthrough.htm||GHIDRA||||END|
@ -339,7 +331,6 @@ src/main/help/help/topics/FrontEndPlugin/images/ConnectTools.png||GHIDRA||||END|
src/main/help/help/topics/FrontEndPlugin/images/DeleteProject.png||GHIDRA||||END| src/main/help/help/topics/FrontEndPlugin/images/DeleteProject.png||GHIDRA||||END|
src/main/help/help/topics/FrontEndPlugin/images/EditPluginPath.png||GHIDRA||||END| src/main/help/help/topics/FrontEndPlugin/images/EditPluginPath.png||GHIDRA||||END|
src/main/help/help/topics/FrontEndPlugin/images/EditProjectAccessList.png||GHIDRA||||END| src/main/help/help/topics/FrontEndPlugin/images/EditProjectAccessList.png||GHIDRA||||END|
src/main/help/help/topics/FrontEndPlugin/images/EditProjectAccessPanel.png||GHIDRA||||END|
src/main/help/help/topics/FrontEndPlugin/images/MemoryUsage.png||GHIDRA||||END| src/main/help/help/topics/FrontEndPlugin/images/MemoryUsage.png||GHIDRA||||END|
src/main/help/help/topics/FrontEndPlugin/images/NonSharedProjectInfo.png||GHIDRA||||END| src/main/help/help/topics/FrontEndPlugin/images/NonSharedProjectInfo.png||GHIDRA||||END|
src/main/help/help/topics/FrontEndPlugin/images/OpenProject.png||GHIDRA||||END| src/main/help/help/topics/FrontEndPlugin/images/OpenProject.png||GHIDRA||||END|
@ -366,7 +357,6 @@ src/main/help/help/topics/FrontEndPlugin/images/VersionedFileCOnoServer.png||GHI
src/main/help/help/topics/FrontEndPlugin/images/VersionedFileCOwithServer.png||GHIDRA||||END| src/main/help/help/topics/FrontEndPlugin/images/VersionedFileCOwithServer.png||GHIDRA||||END|
src/main/help/help/topics/FrontEndPlugin/images/VersionedFileIcon.png||GHIDRA||||END| src/main/help/help/topics/FrontEndPlugin/images/VersionedFileIcon.png||GHIDRA||||END|
src/main/help/help/topics/FrontEndPlugin/images/ViewOtherProjects.png||GHIDRA||||END| src/main/help/help/topics/FrontEndPlugin/images/ViewOtherProjects.png||GHIDRA||||END|
src/main/help/help/topics/FrontEndPlugin/images/ViewProjectAccessPanel.png||GHIDRA||||END|
src/main/help/help/topics/FrontEndPlugin/images/closedBookBlue.png||GHIDRA||reviewed||END| src/main/help/help/topics/FrontEndPlugin/images/closedBookBlue.png||GHIDRA||reviewed||END|
src/main/help/help/topics/FrontEndPlugin/images/connected.gif||GHIDRA||||END| src/main/help/help/topics/FrontEndPlugin/images/connected.gif||GHIDRA||||END|
src/main/help/help/topics/FrontEndPlugin/images/disconnected.gif||GHIDRA||||END| src/main/help/help/topics/FrontEndPlugin/images/disconnected.gif||GHIDRA||||END|
@ -378,7 +368,6 @@ src/main/help/help/topics/FrontEndPlugin/images/up.png||GHIDRA||||END|
src/main/help/help/topics/FrontEndPlugin/images/user.png||FAMFAMFAM Icons - CC 2.5|||famfamfam silk icon set|END| src/main/help/help/topics/FrontEndPlugin/images/user.png||FAMFAMFAM Icons - CC 2.5|||famfamfam silk icon set|END|
src/main/help/help/topics/FunctionComparison/FunctionComparison.htm||GHIDRA||||END| src/main/help/help/topics/FunctionComparison/FunctionComparison.htm||GHIDRA||||END|
src/main/help/help/topics/FunctionComparison/images/FunctionComparisonWindow.png||GHIDRA||||END| src/main/help/help/topics/FunctionComparison/images/FunctionComparisonWindow.png||GHIDRA||||END|
src/main/help/help/topics/FunctionComparison/images/FunctionComparisonWindowFromMap.png||GHIDRA||||END|
src/main/help/help/topics/FunctionComparison/images/FunctionScope.gif||GHIDRA||||END| src/main/help/help/topics/FunctionComparison/images/FunctionScope.gif||GHIDRA||||END|
src/main/help/help/topics/FunctionComparison/images/ListingCodeComparisonOptions.png||GHIDRA||||END| src/main/help/help/topics/FunctionComparison/images/ListingCodeComparisonOptions.png||GHIDRA||||END|
src/main/help/help/topics/FunctionComparison/images/MultiFunctionComparisonWindow.png||GHIDRA||||END| src/main/help/help/topics/FunctionComparison/images/MultiFunctionComparisonWindow.png||GHIDRA||||END|
@ -410,7 +399,6 @@ src/main/help/help/topics/FunctionTagPlugin/images/DeleteWarning.png||GHIDRA||||
src/main/help/help/topics/FunctionTagPlugin/images/EditNotAllowedWarning.png||GHIDRA||||END| src/main/help/help/topics/FunctionTagPlugin/images/EditNotAllowedWarning.png||GHIDRA||||END|
src/main/help/help/topics/FunctionTagPlugin/images/EditTag.png||GHIDRA||||END| src/main/help/help/topics/FunctionTagPlugin/images/EditTag.png||GHIDRA||||END|
src/main/help/help/topics/FunctionTagPlugin/images/FilterField.png||GHIDRA||||END| src/main/help/help/topics/FunctionTagPlugin/images/FilterField.png||GHIDRA||||END|
src/main/help/help/topics/FunctionTagPlugin/images/FullWindow.png||GHIDRA||||END|
src/main/help/help/topics/FunctionTagPlugin/images/FunctionTagPlugin.png||GHIDRA||||END| src/main/help/help/topics/FunctionTagPlugin/images/FunctionTagPlugin.png||GHIDRA||||END|
src/main/help/help/topics/FunctionTagPlugin/images/InputField.png||GHIDRA||||END| src/main/help/help/topics/FunctionTagPlugin/images/InputField.png||GHIDRA||||END|
src/main/help/help/topics/FunctionWindowPlugin/function_window.htm||GHIDRA||||END| src/main/help/help/topics/FunctionWindowPlugin/function_window.htm||GHIDRA||||END|
@ -534,10 +522,6 @@ src/main/help/help/topics/OverviewPlugin/images/EntropyLegend.png||GHIDRA||||END
src/main/help/help/topics/OverviewPlugin/images/EntropyOptions.png||GHIDRA||||END| src/main/help/help/topics/OverviewPlugin/images/EntropyOptions.png||GHIDRA||||END|
src/main/help/help/topics/OverviewPlugin/images/Equation.png||GHIDRA||||END| src/main/help/help/topics/OverviewPlugin/images/Equation.png||GHIDRA||||END|
src/main/help/help/topics/OverviewPlugin/images/OverviewPanel.png||GHIDRA||||END| src/main/help/help/topics/OverviewPlugin/images/OverviewPanel.png||GHIDRA||||END|
src/main/help/help/topics/OverviewPlugin/images/Program.gif||GHIDRA||||END|
src/main/help/help/topics/OverviewPlugin/images/View.gif||GHIDRA||||END|
src/main/help/help/topics/OverviewPlugin/images/zoom_in.png||FAMFAMFAM Icons - CC 2.5|||silk|END|
src/main/help/help/topics/OverviewPlugin/images/zoom_out.png||FAMFAMFAM Icons - CC 2.5|||silk|END|
src/main/help/help/topics/PDB/PDB.htm||GHIDRA||||END| src/main/help/help/topics/PDB/PDB.htm||GHIDRA||||END|
src/main/help/help/topics/PDB/download_pdb_file.html||GHIDRA||||END| src/main/help/help/topics/PDB/download_pdb_file.html||GHIDRA||||END|
src/main/help/help/topics/PDB/images/KnownSymbolServerURLsDialog.png||GHIDRA||||END| src/main/help/help/topics/PDB/images/KnownSymbolServerURLsDialog.png||GHIDRA||||END|
@ -689,11 +673,9 @@ src/main/help/help/topics/Search/images/DOSA_D.png||GHIDRA||||END|
src/main/help/help/topics/Search/images/DOSA_O.png||GHIDRA||||END| src/main/help/help/topics/Search/images/DOSA_O.png||GHIDRA||||END|
src/main/help/help/topics/Search/images/DOSA_S.png||GHIDRA||||END| src/main/help/help/topics/Search/images/DOSA_S.png||GHIDRA||||END|
src/main/help/help/topics/Search/images/DirectReferences.png||GHIDRA||||END| src/main/help/help/topics/Search/images/DirectReferences.png||GHIDRA||||END|
src/main/help/help/topics/Search/images/DirectRefsOnSelection.png||GHIDRA||||END|
src/main/help/help/topics/Search/images/MultipleSelectionError.png||GHIDRA||||END| src/main/help/help/topics/Search/images/MultipleSelectionError.png||GHIDRA||||END|
src/main/help/help/topics/Search/images/QueryResultsSearch.png||GHIDRA||||END| src/main/help/help/topics/Search/images/QueryResultsSearch.png||GHIDRA||||END|
src/main/help/help/topics/Search/images/SearchForAddressTables.png||GHIDRA||||END| src/main/help/help/topics/Search/images/SearchForAddressTables.png||GHIDRA||||END|
src/main/help/help/topics/Search/images/SearchInThstructionPatternsResultsTable.png||GHIDRA||||END|
src/main/help/help/topics/Search/images/SearchInstructionPatterns.png||GHIDRA||||END| src/main/help/help/topics/Search/images/SearchInstructionPatterns.png||GHIDRA||||END|
src/main/help/help/topics/Search/images/SearchInstructionPatternsControlPanel.png||GHIDRA||||END| src/main/help/help/topics/Search/images/SearchInstructionPatternsControlPanel.png||GHIDRA||||END|
src/main/help/help/topics/Search/images/SearchInstructionPatternsInstructionTable.png||GHIDRA||||END| src/main/help/help/topics/Search/images/SearchInstructionPatternsInstructionTable.png||GHIDRA||||END|
@ -716,7 +698,6 @@ src/main/help/help/topics/Search/images/SearchText.png||GHIDRA||||END|
src/main/help/help/topics/Search/images/StringSearchDialog.png||GHIDRA||||END| src/main/help/help/topics/Search/images/StringSearchDialog.png||GHIDRA||||END|
src/main/help/help/topics/Search/images/StringSearchResults.png||GHIDRA||||END| src/main/help/help/topics/Search/images/StringSearchResults.png||GHIDRA||||END|
src/main/help/help/topics/Search/images/binaryData.gif||GHIDRA||||END| src/main/help/help/topics/Search/images/binaryData.gif||GHIDRA||||END|
src/main/help/help/topics/Search/images/browser.png||Nuvola Icons - LGPL 2.1|||Nuvola icon set|END|
src/main/help/help/topics/Search/images/collapse.gif||GHIDRA||||END| src/main/help/help/topics/Search/images/collapse.gif||GHIDRA||||END|
src/main/help/help/topics/Search/images/dialog-warning.png||Oxygen Icons - LGPL 3.0||||END| src/main/help/help/topics/Search/images/dialog-warning.png||Oxygen Icons - LGPL 3.0||||END|
src/main/help/help/topics/Search/images/dialog-warning_red.png||Tango Icons - Public Domain||||END| src/main/help/help/topics/Search/images/dialog-warning_red.png||Tango Icons - Public Domain||||END|
@ -726,7 +707,6 @@ src/main/help/help/topics/Search/images/expand.gif||GHIDRA||||END|
src/main/help/help/topics/Search/images/font.png||FAMFAMFAM Icons - CC 2.5||||END| src/main/help/help/topics/Search/images/font.png||FAMFAMFAM Icons - CC 2.5||||END|
src/main/help/help/topics/Search/images/go-home.png||Tango Icons - Public Domain||||END| src/main/help/help/topics/Search/images/go-home.png||Tango Icons - Public Domain||||END|
src/main/help/help/topics/Search/images/hexData.png||GHIDRA||||END| src/main/help/help/topics/Search/images/hexData.png||GHIDRA||||END|
src/main/help/help/topics/Search/images/locationOut.gif||GHIDRA||||END|
src/main/help/help/topics/Search/images/magnifier.png||FAMFAMFAM Icons - CC 2.5||||END| src/main/help/help/topics/Search/images/magnifier.png||FAMFAMFAM Icons - CC 2.5||||END|
src/main/help/help/topics/Search/images/page_white_copy.png||FAMFAMFAM Icons - CC 2.5|||famfamfam silk icon set|END| src/main/help/help/topics/Search/images/page_white_copy.png||FAMFAMFAM Icons - CC 2.5|||famfamfam silk icon set|END|
src/main/help/help/topics/Search/images/reload.png||Nuvola Icons - LGPL 2.1||||END| src/main/help/help/topics/Search/images/reload.png||Nuvola Icons - LGPL 2.1||||END|
@ -734,7 +714,6 @@ src/main/help/help/topics/Search/images/searchm_obj.gif||GHIDRA||||END|
src/main/help/help/topics/Search/images/table_delete.png||FAMFAMFAM Icons - CC 2.5|||famfamfam silk icon set|END| src/main/help/help/topics/Search/images/table_delete.png||FAMFAMFAM Icons - CC 2.5|||famfamfam silk icon set|END|
src/main/help/help/topics/Search/images/text_align_justify.png||FAMFAMFAM Icons - CC 2.5|||famfamfam silk icon set|END| src/main/help/help/topics/Search/images/text_align_justify.png||FAMFAMFAM Icons - CC 2.5|||famfamfam silk icon set|END|
src/main/help/help/topics/Search/images/view-filter.png||Oxygen Icons - LGPL 3.0|||Oxygen icon theme (dual license; LGPL or CC-SA-3.0)|END| src/main/help/help/topics/Search/images/view-filter.png||Oxygen Icons - LGPL 3.0|||Oxygen icon theme (dual license; LGPL or CC-SA-3.0)|END|
src/main/help/help/topics/Search/images/viewmag.png||Nuvola Icons - LGPL 2.1|||Nuvola icon set|END|
src/main/help/help/topics/SelectBlockPlugin/Select_Block_Help.html||GHIDRA||||END| src/main/help/help/topics/SelectBlockPlugin/Select_Block_Help.html||GHIDRA||||END|
src/main/help/help/topics/SelectBlockPlugin/images/Dialog.png||GHIDRA||||END| src/main/help/help/topics/SelectBlockPlugin/images/Dialog.png||GHIDRA||||END|
src/main/help/help/topics/SelectBlockPlugin/images/ToBadAddr.png||GHIDRA||||END| src/main/help/help/topics/SelectBlockPlugin/images/ToBadAddr.png||GHIDRA||||END|
@ -780,7 +759,6 @@ src/main/help/help/topics/SymbolTreePlugin/images/EditExternalLocation.png||GHID
src/main/help/help/topics/SymbolTreePlugin/images/SymbolTree.png||GHIDRA||||END| src/main/help/help/topics/SymbolTreePlugin/images/SymbolTree.png||GHIDRA||||END|
src/main/help/help/topics/SymbolTreePlugin/images/openFolderGroup.png||Modified Nuvola Icons - LGPL 2.1||||END| src/main/help/help/topics/SymbolTreePlugin/images/openFolderGroup.png||Modified Nuvola Icons - LGPL 2.1||||END|
src/main/help/help/topics/SymbolTreePlugin/images/sitemap_color.png||FAMFAMFAM Icons - CC 2.5|||famfamfam silk icon set|END| src/main/help/help/topics/SymbolTreePlugin/images/sitemap_color.png||FAMFAMFAM Icons - CC 2.5|||famfamfam silk icon set|END|
src/main/help/help/topics/SymbolTreePlugin/locationIn.gif||GHIDRA||||END|
src/main/help/help/topics/Tables/GhidraTableHeaders.html||GHIDRA||||END| src/main/help/help/topics/Tables/GhidraTableHeaders.html||GHIDRA||||END|
src/main/help/help/topics/Tables/images/BytesSettingsDialog.png||GHIDRA||reviewed||END| src/main/help/help/topics/Tables/images/BytesSettingsDialog.png||GHIDRA||reviewed||END|
src/main/help/help/topics/Tables/images/MultipleColumnSortDialog.png||GHIDRA||||END| src/main/help/help/topics/Tables/images/MultipleColumnSortDialog.png||GHIDRA||||END|

View file

@ -23,6 +23,8 @@
If you are using a table that does not offer one of the above features, and you would like If you are using a table that does not offer one of the above features, and you would like
us to add that feature, then please contact the Ghidra team to request additional support. us to add that feature, then please contact the Ghidra team to request additional support.
</p> </p>
<blockquote>
<p> <p>
<img src="../../shared/note.png" border="0">In addition to table header actions, many <img src="../../shared/note.png" border="0">In addition to table header actions, many
tables support a common set of actions when right-clicking in the body of the table, such tables support a common set of actions when right-clicking in the body of the table, such
@ -34,6 +36,7 @@
<li><a href="#SelectAll">Select All (also available via <b>Ctrl-A</b></a>.</li> <li><a href="#SelectAll">Select All (also available via <b>Ctrl-A</b></a>.</li>
</ol> </ol>
</p> </p>
</blockquote>
</blockquote> </blockquote>
@ -45,10 +48,12 @@
sorted column is denoted by an icon that shows the sort direction. sorted column is denoted by an icon that shows the sort direction.
</p> </p>
<p><img border="0" src="../../shared/note.png" alt=""> <blockquote>
<p><img border="0" src="../../shared/note.yellow.png" alt="">
If you click a table column header and no sorting takes place, then that particular If you click a table column header and no sorting takes place, then that particular
table does not support sorting on columns. table does not support sorting on columns.
</p> </p>
</blockquote>
<p> <p>
When you click a column to sort the data, the default sort will be ascending. If you When you click a column to sort the data, the default sort will be ascending. If you
@ -73,17 +78,27 @@
</p> </p>
<p> <p>
With multiple sorted columns, you may change the direction of any individual With multiple sorted columns, you may change the direction of any individual
sort column by left-clicking that column. To remove a sort column from a multiple sort column by left-clicking that column.
column sort, Ctrl-left-click that column. <blockquote>
<p><img border="0" src="../../shared/tip.png" alt="">
To remove a sort column from a multiple column sort, Ctrl-left-click that column.
This will even work when only one column is sorted, <b><i>thus effectively disabling
sorting for the table</i></b>.
</p>
</blockquote>
</p> </p>
<blockquote>
<p><img border="0" src="../../shared/note.png" alt=""> <p><img border="0" src="../../shared/note.png" alt="">
It is possible to cancel some tables while they are loading or sorting their data. It is possible to cancel some tables while they are loading or sorting their data.
If this happens, you can trigger a reload of the data by sorting on one of the If this happens, you can trigger a reload of the data by sorting on one of the
columns. columns.
</p> </p>
</blockquote>
</blockquote> </blockquote>
<br>
<br>
<h2><a name="SelectColumns"></a>Choosing Columns</h2> <h2><a name="SelectColumns"></a>Choosing Columns</h2>
@ -159,19 +174,17 @@
<blockquote> <blockquote>
<p> <p>
Exports the current table to a comma-separated value (CSV) text file. The CSV file Exports the current table to a comma-separated value (CSV) text file. The CSV file
will export the selected columns in the order displayed. will export the selected columns in the order displayed. The first row of ouput will
contain the table's column names.
<blockquote>
<p> <p>
<img src="../../shared/note.png" border="0">This action uses the current <img src="../../shared/note.yellow.png" border="0">This action uses the current
table selection when deciding what to export. If no row is selected, then no data is table selection when deciding what to export. If no row is selected, then no data is
exported. To select all rows, use the <b>Select All</b> action or press <b>Ctrl-A</b> on exported. To select all rows, use the <b>Select All</b> action or press <b>Ctrl-A</b> on
the keyboard. the keyboard.
</p> </p>
</blockquote>
<p>
<img src="../../shared/note.png" border="0">
Note: The first row will contain the column names.
</p>
</p> </p>
</blockquote> </blockquote>

View file

@ -112,7 +112,7 @@ class CommentWindowProvider extends ComponentProviderAdapter {
commentModel.addTableModelListener(e -> { commentModel.addTableModelListener(e -> {
int rowCount = commentModel.getRowCount(); int rowCount = commentModel.getRowCount();
int unfilteredCount = commentModel.getUnfilteredCount(); int unfilteredCount = commentModel.getUnfilteredRowCount();
StringBuilder buffy = new StringBuilder(); StringBuilder buffy = new StringBuilder();

View file

@ -16,20 +16,18 @@
package ghidra.app.plugin.core.data; package ghidra.app.plugin.core.data;
import java.awt.*; import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import javax.swing.*; import javax.swing.*;
import javax.swing.border.EmptyBorder; import javax.swing.border.EmptyBorder;
import javax.swing.event.TableModelEvent; import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.event.TableModelListener; import javax.swing.table.TableCellEditor;
import javax.swing.table.*;
import docking.DialogComponentProvider; import docking.DialogComponentProvider;
import docking.widgets.combobox.GComboBox; import docking.widgets.combobox.GComboBox;
import docking.widgets.dialogs.StringChoices; import docking.widgets.dialogs.StringChoices;
import docking.widgets.table.DefaultSortedTableModel; import docking.widgets.table.AbstractSortedTableModel;
import docking.widgets.table.GTable; import docking.widgets.table.GTable;
import ghidra.docking.settings.*; import ghidra.docking.settings.*;
import ghidra.program.model.data.Composite; import ghidra.program.model.data.Composite;
@ -67,9 +65,6 @@ public class DataSettingsDialog extends DialogComponentProvider {
private boolean appliedSettings; private boolean appliedSettings;
private Program program; private Program program;
/**
* Construct instance settings dialog.
*/
public DataSettingsDialog(Program program, ProgramSelection sel) throws CancelledException { public DataSettingsDialog(Program program, ProgramSelection sel) throws CancelledException {
super("Data Settings", true, false, true, false); super("Data Settings", true, false, true, false);
this.program = program; this.program = program;
@ -83,9 +78,6 @@ public class DataSettingsDialog extends DialogComponentProvider {
buildPanel(); buildPanel();
} }
/**
* Construct instance settings dialog.
*/
public DataSettingsDialog(Program program, Data data) { public DataSettingsDialog(Program program, Data data) {
super("Data Settings", true, false, true, false); super("Data Settings", true, false, true, false);
this.data = data; this.data = data;
@ -111,9 +103,6 @@ public class DataSettingsDialog extends DialogComponentProvider {
buildPanel(); buildPanel();
} }
/**
* Construct default data type settings dialog.
*/
public DataSettingsDialog(Program program, DataType dataType) { public DataSettingsDialog(Program program, DataType dataType) {
super("Data Settings", true, false, true, false); super("Data Settings", true, false, true, false);
this.dataType = dataType; this.dataType = dataType;
@ -126,9 +115,6 @@ public class DataSettingsDialog extends DialogComponentProvider {
setHelpLocation(new HelpLocation("DataPlugin", "Default_Data_Settings")); setHelpLocation(new HelpLocation("DataPlugin", "Default_Data_Settings"));
} }
/**
* Construct default data type component settings dialog.
*/
DataSettingsDialog(Program program, DataTypeComponent dtc) { DataSettingsDialog(Program program, DataTypeComponent dtc) {
super("Data Settings", true, false, true, false); super("Data Settings", true, false, true, false);
this.dtc = dtc; this.dtc = dtc;
@ -204,12 +190,7 @@ public class DataSettingsDialog extends DialogComponentProvider {
addOKButton(); addOKButton();
JButton newApplyButton = new JButton("Apply"); JButton newApplyButton = new JButton("Apply");
newApplyButton.addActionListener(new ActionListener() { newApplyButton.addActionListener(e -> applySettings());
@Override
public void actionPerformed(ActionEvent e) {
applySettings();
}
});
addButton(newApplyButton); addButton(newApplyButton);
addCancelButton(); addCancelButton();
@ -219,17 +200,9 @@ public class DataSettingsDialog extends DialogComponentProvider {
JPanel workPanel = new JPanel(new BorderLayout()); JPanel workPanel = new JPanel(new BorderLayout());
workPanel.setBorder(new EmptyBorder(10, 10, 10, 10)); workPanel.setBorder(new EmptyBorder(10, 10, 10, 10));
settingsTableModel = new SettingsTableModel(); settingsTableModel = new SettingsTableModel(settingsDefs);
settingsTableModel.addTableModelListener(e -> appliedSettings = false);
DefaultSortedTableModel sorter = new DefaultSortedTableModel(settingsTableModel); settingsTable = new GhidraTable(settingsTableModel);
sorter.sortByColumn(SettingsTableModel.DEFAULT_SORT_COL);
sorter.addTableModelListener(new TableModelListener() {
@Override
public void tableChanged(TableModelEvent e) {
appliedSettings = false;
}
});
settingsTable = new GhidraTable(sorter);
settingsTable.setAutoscrolls(true); settingsTable.setAutoscrolls(true);
settingsTable.setRowSelectionAllowed(false); settingsTable.setRowSelectionAllowed(false);
settingsTable.setColumnSelectionAllowed(false); settingsTable.setColumnSelectionAllowed(false);
@ -251,18 +224,12 @@ public class DataSettingsDialog extends DialogComponentProvider {
return workPanel; return workPanel;
} }
/**
* @see ghidra.util.bean.GhidraDialog#cancelCallback()
*/
@Override @Override
protected void cancelCallback() { protected void cancelCallback() {
close(); close();
dispose(); dispose();
} }
/**
* @see ghidra.util.bean.GhidraDialog#okCallback()
*/
@Override @Override
protected void okCallback() { protected void okCallback() {
applySettings(); applySettings();
@ -565,7 +532,7 @@ public class DataSettingsDialog extends DialogComponentProvider {
// For selection case we must ensure that settings has a non-null value even for defaults // For selection case we must ensure that settings has a non-null value even for defaults
if (selection != null && settings.getValue(def.getName()) == null) { if (selection != null && settings.getValue(def.getName()) == null) {
settings.setValue(def.getName(), new Long(def.getChoice(settings))); settings.setValue(def.getName(), Long.valueOf(def.getChoice(settings)));
} }
} }
@ -594,81 +561,98 @@ public class DataSettingsDialog extends DialogComponentProvider {
return newChoices; return newChoices;
} }
class SettingsTableModel extends AbstractTableModel { //==================================================================================================
// Inner Classes
//==================================================================================================
private static final long serialVersionUID = 1L; class SettingsRowObject {
static final int DEFAULT_SORT_COL = 0; private SettingsDefinition definition;
SettingsRowObject(SettingsDefinition definition) {
this.definition = definition;
}
public String getName() {
return definition.getName();
}
Object getSettingsChoices() {
if (definition instanceof EnumSettingsDefinition) {
StringChoices choices = getChoices((EnumSettingsDefinition) definition);
return choices;
}
else if (definition instanceof BooleanSettingsDefinition) {
StringChoices choices = getChoices((BooleanSettingsDefinition) definition);
return choices;
}
return "<Unsupported>";
}
boolean useDefault() {
if (definition instanceof EnumSettingsDefinition) {
EnumSettingsDefinition def = (EnumSettingsDefinition) definition;
return def.getChoice(settings) == def.getChoice(defaultSettings);
}
else if (definition instanceof BooleanSettingsDefinition) {
BooleanSettingsDefinition def = (BooleanSettingsDefinition) definition;
return def.getValue(settings) == def.getValue(defaultSettings);
}
return false;
}
boolean setSettingsChoice(Object value) {
if (definition instanceof EnumSettingsDefinition) {
setChoice(value, (EnumSettingsDefinition) definition);
return true;
}
else if (definition instanceof BooleanSettingsDefinition) {
setChoice(value, (BooleanSettingsDefinition) definition);
return true;
}
return false;
}
void clear(SettingsImpl s) {
definition.clear(s);
}
}
private class SettingsTableModel extends AbstractSortedTableModel<SettingsRowObject> {
private List<SettingsRowObject> rows = new ArrayList<>();
SettingsTableModel(SettingsDefinition[] settingsDefs) {
for (SettingsDefinition sd : settingsDefs) {
rows.add(new SettingsRowObject(sd));
}
}
@Override
public List<SettingsRowObject> getModelData() {
return rows;
}
@Override
public String getName() {
return "Settings Definition Model";
}
@Override
public boolean isSortable(int columnIndex) {
return columnIndex == 0;
}
@Override
public boolean isCellEditable(int row, int col) {
return col != 0;
}
@Override @Override
public int getColumnCount() { public int getColumnCount() {
return (selection != null || editingDefaults) ? 2 : 3; return (selection != null || editingDefaults) ? 2 : 3;
} }
@Override
public int getRowCount() {
return settingsDefs != null ? settingsDefs.length : 0;
}
@Override
public Object getValueAt(int row, int col) {
switch (col) {
case 0:
return settingsDefs[row].getName();
case 1:
if (settingsDefs[row] instanceof EnumSettingsDefinition) {
return getChoices((EnumSettingsDefinition) settingsDefs[row]);
}
else if (settingsDefs[row] instanceof BooleanSettingsDefinition) {
return getChoices((BooleanSettingsDefinition) settingsDefs[row]);
}
return "<Unsupported>";
case 2:
if (settingsDefs[row] instanceof EnumSettingsDefinition) {
EnumSettingsDefinition def = (EnumSettingsDefinition) settingsDefs[row];
return new Boolean(
def.getChoice(settings) == def.getChoice(defaultSettings));
}
else if (settingsDefs[row] instanceof BooleanSettingsDefinition) {
BooleanSettingsDefinition def =
(BooleanSettingsDefinition) settingsDefs[row];
return new Boolean(def.getValue(settings) == def.getValue(defaultSettings));
}
break;
}
return null;
}
/**
* @see TableModel#setValueAt(Object, int, int)
*/
@Override
public void setValueAt(Object value, int row, int col) {
switch (col) {
case 1:
if (settingsDefs[row] instanceof EnumSettingsDefinition) {
setChoice(value, (EnumSettingsDefinition) settingsDefs[row]);
fireTableDataChanged();
}
else if (settingsDefs[row] instanceof BooleanSettingsDefinition) {
setChoice(value, (BooleanSettingsDefinition) settingsDefs[row]);
fireTableDataChanged();
}
break;
case 2:
if (((Boolean) value).booleanValue()) {
settingsDefs[row].clear(settings);
fireTableDataChanged();
}
break;
}
}
/**
* @see TableModel#getColumnName(int)
*/
@Override @Override
public String getColumnName(int col) { public String getColumnName(int col) {
switch (col) { switch (col) {
@ -682,40 +666,40 @@ public class DataSettingsDialog extends DialogComponentProvider {
return null; return null;
} }
/**
* @see TableModel#getColumnClass(int)
*/
@Override @Override
public Class<?> getColumnClass(int col) { public Object getColumnValueForRow(SettingsRowObject t, int columnIndex) {
switch (col) { switch (columnIndex) {
case 0: case 0:
return String.class; return t.getName();
case 1: case 1:
return Settings.class; return t.getSettingsChoices();
case 2: case 2:
return Boolean.class; return t.useDefault();
} }
return null; return null;
} }
public boolean isColumnSortable(int col) {
return col == 0;
}
/**
* @see TableModel#isCellEditable(int, int)
*/
@Override @Override
public boolean isCellEditable(int row, int col) { public void setValueAt(Object value, int row, int col) {
return col != 0; SettingsRowObject rowObject = rows.get(row);
switch (col) {
case 1:
if (rowObject.setSettingsChoice(value)) {
fireTableDataChanged();
}
break;
case 2:
if (((Boolean) value).booleanValue()) {
rowObject.clear(settings);
fireTableDataChanged();
}
break;
}
} }
} }
private class SettingsEditor extends AbstractCellEditor implements TableCellEditor { private class SettingsEditor extends AbstractCellEditor implements TableCellEditor {
private static final long serialVersionUID = 1L;
final static int ENUM = 0; final static int ENUM = 0;
final static int BOOLEAN = 1; final static int BOOLEAN = 1;
@ -723,12 +707,7 @@ public class DataSettingsDialog extends DialogComponentProvider {
private GComboBox<String> comboBox = new GComboBox<>(); private GComboBox<String> comboBox = new GComboBox<>();
SettingsEditor() { SettingsEditor() {
comboBox.addItemListener(new ItemListener() { comboBox.addItemListener(e -> fireEditingStopped());
@Override
public void itemStateChanged(ItemEvent e) {
fireEditingStopped();
}
});
} }
/** /**
@ -755,9 +734,6 @@ public class DataSettingsDialog extends DialogComponentProvider {
return enuum; return enuum;
} }
/**
* @see javax.swing.table.TableCellEditor#getTableCellEditorComponent(javax.swing.JTable, java.lang.Object, boolean, int, int)
*/
@Override @Override
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected,
int row, int column) { int row, int column) {

View file

@ -118,7 +118,7 @@ class DataWindowProvider extends ComponentProviderAdapter {
dataModel.addTableModelListener(e -> { dataModel.addTableModelListener(e -> {
int rowCount = dataModel.getRowCount(); int rowCount = dataModel.getRowCount();
int unfilteredCount = dataModel.getUnfilteredCount(); int unfilteredCount = dataModel.getUnfilteredRowCount();
StringBuilder buffy = new StringBuilder(); StringBuilder buffy = new StringBuilder();

View file

@ -133,7 +133,7 @@ public class FunctionWindowProvider extends ComponentProviderAdapter {
functionModel.addTableModelListener(e -> { functionModel.addTableModelListener(e -> {
int rowCount = functionModel.getRowCount(); int rowCount = functionModel.getRowCount();
int unfilteredCount = functionModel.getUnfilteredCount(); int unfilteredCount = functionModel.getUnfilteredRowCount();
StringBuilder buffy = new StringBuilder(); StringBuilder buffy = new StringBuilder();

View file

@ -1,6 +1,5 @@
/* ### /* ###
* IP: GHIDRA * IP: GHIDRA
* REVIEWED: YES
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,6 +15,12 @@
*/ */
package ghidra.app.plugin.core.references; package ghidra.app.plugin.core.references;
import java.awt.Window;
import java.util.*;
import org.apache.commons.lang3.StringUtils;
import docking.widgets.table.AbstractSortedTableModel;
import ghidra.app.cmd.refs.UpdateExternalNameCmd; import ghidra.app.cmd.refs.UpdateExternalNameCmd;
import ghidra.framework.cmd.Command; import ghidra.framework.cmd.Command;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
@ -25,57 +30,40 @@ import ghidra.program.model.symbol.ExternalManager;
import ghidra.program.model.symbol.SourceType; import ghidra.program.model.symbol.SourceType;
import ghidra.util.Msg; import ghidra.util.Msg;
import java.awt.Window; public class ExternalNamesTableModel extends AbstractSortedTableModel<ExternalPath> {
import java.util.*;
import javax.swing.table.AbstractTableModel;
/**
* TableModel for the external program names and corresponding ghidra path names.
*/
class ExternalNamesTableModel extends AbstractTableModel {
final static int NAME_COL = 0; final static int NAME_COL = 0;
final static int PATH_COL = 1; final static int PATH_COL = 1;
final static String EXTERNAL_NAME = "Name"; final static String EXTERNAL_NAME = "Name";
final static String PATH_NAME = "Ghidra Program"; final static String PATH_NAME = "Ghidra Program";
private final List<String> columns = List.of(EXTERNAL_NAME, PATH_NAME);
private final String[] columnNames = { EXTERNAL_NAME, PATH_NAME };
private List<String> nameList = new ArrayList<String>();
private List<String> pathNameList = new ArrayList<String>();
private Program program;
private PluginTool tool; private PluginTool tool;
private Program program;
private List<ExternalPath> paths = new ArrayList<>();
public ExternalNamesTableModel(PluginTool tool) { public ExternalNamesTableModel(PluginTool tool) {
this.tool = tool; this.tool = tool;
} }
@Override
public int getColumnCount() { public int getColumnCount() {
return columnNames.length; return columns.size();
}
public int getRowCount() {
return nameList.size();
} }
@Override @Override
public String getColumnName(int column) { public String getColumnName(int column) {
return columnNames[column]; return columns.get(column);
} }
public Object getValueAt(int rowIndex, int columnIndex) { @Override
if (rowIndex >= nameList.size()) { public String getName() {
return ""; return "External Programs Model";
} }
switch (columnIndex) {
case NAME_COL:
return nameList.get(rowIndex);
case PATH_COL: @Override
return pathNameList.get(rowIndex); public boolean isSortable(int columnIndex) {
} return true;
return "Unknown Column!";
} }
@Override @Override
@ -87,55 +75,81 @@ class ExternalNamesTableModel extends AbstractTableModel {
} }
@Override @Override
public void setValueAt(Object aValue, int rowIndex, int columnIndex) { public List<ExternalPath> getModelData() {
String extName = ((String) aValue).trim(); return paths;
}
if ("".equals(extName)) { @Override
public Object getColumnValueForRow(ExternalPath t, int columnIndex) {
switch (columnIndex) {
case NAME_COL:
return t.getName();
case PATH_COL:
return t.getPath();
}
return "Unknown Column!";
}
@Override
public void setValueAt(Object aValue, int row, int column) {
String newName = ((String) aValue).trim();
if (StringUtils.isBlank(newName)) {
return; return;
} }
if (nameList.contains(extName)) { int index = indexOf(newName);
if (nameList.indexOf(extName) != rowIndex) { if (index >= 0) {
Window window = tool.getActiveWindow(); Window window = tool.getActiveWindow();
Msg.showInfo(getClass(), window, "Duplicate Name", "Name already exists: " + Msg.showInfo(getClass(), window, "Duplicate Name", "Name already exists: " + newName);
extName);
}
return; return;
} }
Command cmd = ExternalPath path = paths.get(row);
new UpdateExternalNameCmd(nameList.get(rowIndex), extName, SourceType.USER_DEFINED); String oldName = path.getName();
Command cmd = new UpdateExternalNameCmd(oldName, newName, SourceType.USER_DEFINED);
if (!tool.execute(cmd, program)) { if (!tool.execute(cmd, program)) {
tool.setStatusInfo(cmd.getStatusMsg()); tool.setStatusInfo(cmd.getStatusMsg());
} }
} }
private int indexOf(String name) {
for (int i = 0; i < paths.size(); i++) {
ExternalPath path = paths.get(i);
if (path.getName().equals(name)) {
return i;
}
}
return 0;
}
void setProgram(Program program) { void setProgram(Program program) {
this.program = program; this.program = program;
updateTableData(); updateTableData();
} }
///////////////////////////////////////////////////////////////////////////
void updateTableData() { void updateTableData() {
nameList.clear();
pathNameList.clear(); paths.clear();
if (program == null) { if (program == null) {
fireTableDataChanged(); fireTableDataChanged();
return; return;
} }
ExternalManager extMgr = program.getExternalManager(); ExternalManager extMgr = program.getExternalManager();
String[] programNames = extMgr.getExternalLibraryNames(); String[] programNames = extMgr.getExternalLibraryNames();
Arrays.sort(programNames); Arrays.sort(programNames);
for (int i = 0; i < programNames.length; i++) { for (String programName : programNames) {
if (Library.UNKNOWN.equals(programNames[i])) { if (Library.UNKNOWN.equals(programName)) {
continue; continue;
} }
nameList.add(programNames[i]);
pathNameList.add(extMgr.getExternalLibraryPath(programNames[i])); ExternalPath path =
new ExternalPath(programName, extMgr.getExternalLibraryPath(programName));
paths.add(path);
} }
fireTableDataChanged(); fireTableDataChanged();
} }

View file

@ -0,0 +1,72 @@
/* ###
* IP: GHIDRA
*
* 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.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.references;
import java.util.Objects;
/**
* A simple container class used as a row object
*/
class ExternalPath {
private String name;
private String path;
ExternalPath(String name, String path) {
this.name = name;
this.path = path;
}
String getName() {
return name;
}
String getPath() {
return path;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result + ((path == null) ? 0 : path.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
ExternalPath other = (ExternalPath) obj;
if (!Objects.equals(name, other.name)) {
return false;
}
if (!Objects.equals(path, other.path)) {
return false;
}
return true;
}
}

View file

@ -24,7 +24,6 @@ import java.util.List;
import javax.swing.*; import javax.swing.*;
import docking.ActionContext; import docking.ActionContext;
import docking.widgets.table.DefaultSortedTableModel;
import ghidra.framework.model.DomainObjectListener; import ghidra.framework.model.DomainObjectListener;
import ghidra.framework.plugintool.ComponentProviderAdapter; import ghidra.framework.plugintool.ComponentProviderAdapter;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
@ -35,7 +34,6 @@ import ghidra.util.table.GhidraTable;
public class ExternalReferencesProvider extends ComponentProviderAdapter { public class ExternalReferencesProvider extends ComponentProviderAdapter {
private JPanel mainPanel; private JPanel mainPanel;
private ExternalNamesTableModel tableModel; private ExternalNamesTableModel tableModel;
private DefaultSortedTableModel sortedModel;
private GhidraTable table; private GhidraTable table;
private Program program; private Program program;
@ -106,9 +104,7 @@ public class ExternalReferencesProvider extends ComponentProviderAdapter {
JPanel panel = new JPanel(new BorderLayout()); JPanel panel = new JPanel(new BorderLayout());
tableModel = new ExternalNamesTableModel(tool); tableModel = new ExternalNamesTableModel(tool);
sortedModel = new DefaultSortedTableModel(tableModel); table = new GhidraTable(tableModel);
sortedModel.sortByColumn(ExternalNamesTableModel.NAME_COL);
table = new GhidraTable(sortedModel);
InputMap inputMap = table.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); InputMap inputMap = table.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
KeyStroke enter = KeyStroke.getKeyStroke("ENTER"); KeyStroke enter = KeyStroke.getKeyStroke("ENTER");
@ -150,40 +146,6 @@ public class ExternalReferencesProvider extends ComponentProviderAdapter {
panel.add(sp, BorderLayout.CENTER); panel.add(sp, BorderLayout.CENTER);
// addButton = new JButton("Add");
// addButton.setMnemonic('A');
// addButton.addActionListener(new ActionListener() {
// public void actionPerformed(ActionEvent e) {
// clearStatusText();
// tableModel.addDefaultRow();
// }
// });
// ToolTipManager.setToolTipText(addButton,
// "Link new External Program name to blank Ghidra Pathname");
//
// clearButton = new JButton("Clear");
// clearButton.setMnemonic('C');
// clearButton.addActionListener(new ActionListener() {
// public void actionPerformed(ActionEvent e) {
// clear();
// }
// });
// ToolTipManager.setToolTipText(clearButton, "Remove External Program Link");
// editButton = new JButton("Edit");
// editButton.setMnemonic('E');
// editButton.addActionListener(new ActionListener() {
// public void actionPerformed(ActionEvent e) {
// editPathName();
// }
// });
// ToolTipManager.setToolTipText(editButton, "Edit Ghidra Pathname");
//
// addButton(addButton);
// addButton(editButton);
// addButton(clearButton);
// enableButtons();
return panel; return panel;
} }
@ -201,9 +163,8 @@ public class ExternalReferencesProvider extends ComponentProviderAdapter {
public List<String> getSelectedExternalNames() { public List<String> getSelectedExternalNames() {
List<String> externalNames = new ArrayList<>(); List<String> externalNames = new ArrayList<>();
int[] selectedRows = table.getSelectedRows(); int[] selectedRows = table.getSelectedRows();
for (int selectedRow : selectedRows) { for (int row : selectedRows) {
int index = sortedModel.getSortedIndex(selectedRow); String externalName = (String) tableModel.getValueAt(row, 0);
String externalName = (String) tableModel.getValueAt(index, 0);
externalNames.add(externalName); externalNames.add(externalName);
} }
return externalNames; return externalNames;

View file

@ -134,7 +134,7 @@ public class StringTableProvider extends ComponentProviderAdapter implements Dom
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
int rowCount = stringModel.getRowCount(); int rowCount = stringModel.getRowCount();
int unfilteredCount = stringModel.getUnfilteredCount(); int unfilteredCount = stringModel.getUnfilteredRowCount();
builder.append(rowCount); builder.append(rowCount);
builder.append(" items"); builder.append(" items");

View file

@ -124,7 +124,7 @@ public class ViewStringsProvider extends ComponentProviderAdapter {
stringModel.addTableModelListener(e -> { stringModel.addTableModelListener(e -> {
int rowCount = stringModel.getRowCount(); int rowCount = stringModel.getRowCount();
int unfilteredCount = stringModel.getUnfilteredCount(); int unfilteredCount = stringModel.getUnfilteredRowCount();
setSubTitle("" + rowCount + " items" + setSubTitle("" + rowCount + " items" +
(rowCount != unfilteredCount ? " (of " + unfilteredCount + ")" : "")); (rowCount != unfilteredCount ? " (of " + unfilteredCount + ")" : ""));

View file

@ -119,7 +119,7 @@ class SymbolPanel extends JPanel {
protected RowFilterTransformer<SymbolRowObject> updateRowDataTransformer(boolean nameOnly) { protected RowFilterTransformer<SymbolRowObject> updateRowDataTransformer(boolean nameOnly) {
if (nameOnly) { if (nameOnly) {
return new NameOnlyRowTransformer(); return new NameOnlyRowTransformer(tableModel);
} }
return new DefaultRowFilterTransformer<>(tableModel, symTable.getColumnModel()); return new DefaultRowFilterTransformer<>(tableModel, symTable.getColumnModel());
@ -201,17 +201,42 @@ class SymbolPanel extends JPanel {
// Inner Classes // Inner Classes
//================================================================================================== //==================================================================================================
private class NameOnlyRowTransformer implements RowFilterTransformer<SymbolRowObject> { private static class NameOnlyRowTransformer implements RowFilterTransformer<SymbolRowObject> {
private List<String> list = new ArrayList<>(); private List<String> list = new ArrayList<>();
private SymbolTableModel model;
NameOnlyRowTransformer(SymbolTableModel model) {
this.model = model;
}
@Override @Override
public List<String> transform(SymbolRowObject rowObject) { public List<String> transform(SymbolRowObject rowObject) {
list.clear(); list.clear();
Symbol symbol = tableModel.getSymbolForRowObject(rowObject); Symbol symbol = model.getSymbolForRowObject(rowObject);
if (symbol != null) { if (symbol != null) {
list.add(symbol.getName()); list.add(symbol.getName());
} }
return list; return list;
} }
@Override
public int hashCode() {
// not meant to put in hashing structures; the data for equals may change over time
throw new UnsupportedOperationException();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
return true;
}
} }
} }

View file

@ -152,7 +152,7 @@ class SymbolProvider extends ComponentProviderAdapter {
private String generateSubTitle() { private String generateSubTitle() {
SymbolFilter filter = symbolKeyModel.getFilter(); SymbolFilter filter = symbolKeyModel.getFilter();
int rowCount = symbolKeyModel.getRowCount(); int rowCount = symbolKeyModel.getRowCount();
int unfilteredCount = symbolKeyModel.getUnfilteredCount(); int unfilteredCount = symbolKeyModel.getUnfilteredRowCount();
if (rowCount != unfilteredCount) { if (rowCount != unfilteredCount) {
return " (Text filter matched " + rowCount + " of " + unfilteredCount + " symbols)"; return " (Text filter matched " + rowCount + " of " + unfilteredCount + " symbols)";

View file

@ -65,18 +65,15 @@ public abstract class AddressBasedTableModel<ROW_TYPE> extends GhidraProgramTabl
ROW_TYPE rowObject = filteredData.get(row); ROW_TYPE rowObject = filteredData.get(row);
DynamicTableColumn<ROW_TYPE, ?, ?> tableColumn = getColumn(column); DynamicTableColumn<ROW_TYPE, ?, ?> tableColumn = getColumn(column);
if (tableColumn != null) { if (tableColumn instanceof ProgramLocationTableColumn<?, ?>) {
if (tableColumn instanceof ProgramLocationTableColumn<?, ?>) {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked") // we checked
// we checked ProgramLocationTableColumn<ROW_TYPE, ?> programField =
ProgramLocationTableColumn<ROW_TYPE, ?> programField = (ProgramLocationTableColumn<ROW_TYPE, ?>) tableColumn;
(ProgramLocationTableColumn<ROW_TYPE, ?>) tableColumn; ProgramLocation loc = programField.getProgramLocation(rowObject,
ProgramLocation loc = programField.getProgramLocation(rowObject, getColumnSettings(column), getProgram(), serviceProvider);
getColumnSettings(column), getProgram(), serviceProvider); if (loc != null) {
if (loc != null) { return loc;
return loc;
}
} }
} }

View file

@ -23,7 +23,6 @@ import javax.swing.event.ListSelectionListener;
import javax.swing.table.TableCellRenderer; import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableModel; import javax.swing.table.TableModel;
import docking.widgets.table.DefaultSortedTableModel;
import docking.widgets.table.GTable; import docking.widgets.table.GTable;
import ghidra.app.nav.Navigatable; import ghidra.app.nav.Navigatable;
import ghidra.app.services.GoToService; import ghidra.app.services.GoToService;
@ -214,10 +213,6 @@ public class GhidraTable extends GTable {
if (model instanceof ProgramTableModel) { if (model instanceof ProgramTableModel) {
return (ProgramTableModel) model; return (ProgramTableModel) model;
} }
else if (model instanceof DefaultSortedTableModel) {
DefaultSortedTableModel defaultSortedTableModel = (DefaultSortedTableModel) model;
return getProgramTableModel(defaultSortedTableModel.getModel());
}
return null; return null;
} }

View file

@ -30,6 +30,7 @@ import ghidra.program.util.ProgramSelection;
/** /**
* This is a "program aware" version of GTableFilterPanel * This is a "program aware" version of GTableFilterPanel
* @param <ROW_OBJECT> the row type
*/ */
public class GhidraTableFilterPanel<ROW_OBJECT> extends GTableFilterPanel<ROW_OBJECT> { public class GhidraTableFilterPanel<ROW_OBJECT> extends GTableFilterPanel<ROW_OBJECT> {

View file

@ -26,14 +26,17 @@ import docking.ActionContext;
import docking.action.DockingActionIf; import docking.action.DockingActionIf;
import docking.action.MenuData; import docking.action.MenuData;
import docking.widgets.dialogs.StringChoices; import docking.widgets.dialogs.StringChoices;
import docking.widgets.table.AbstractSortedTableModel;
import ghidra.app.LocationCallback; import ghidra.app.LocationCallback;
import ghidra.app.context.ListingActionContext; import ghidra.app.context.ListingActionContext;
import ghidra.app.events.ProgramLocationPluginEvent; import ghidra.app.events.ProgramLocationPluginEvent;
import ghidra.app.events.ProgramSelectionPluginEvent; import ghidra.app.events.ProgramSelectionPluginEvent;
import ghidra.app.plugin.core.clear.ClearCmd; import ghidra.app.plugin.core.clear.ClearCmd;
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin; import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
import ghidra.app.plugin.core.data.DataSettingsDialog.SettingsRowObject;
import ghidra.app.plugin.core.navigation.NextPrevAddressPlugin; import ghidra.app.plugin.core.navigation.NextPrevAddressPlugin;
import ghidra.docking.settings.*; import ghidra.docking.settings.FormatSettingsDefinition;
import ghidra.docking.settings.SettingsDefinition;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.*; import ghidra.program.model.address.*;
import ghidra.program.model.data.*; import ghidra.program.model.data.*;
@ -215,7 +218,7 @@ public abstract class AbstractDataActionTest extends AbstractGhidraHeadedIntegra
waitForSwing(); waitForSwing();
Runnable r = () -> { Runnable r = () -> {
DataSettingsDialog.SettingsTableModel model = dlg.getSettingsTableModel(); AbstractSortedTableModel<SettingsRowObject> model = dlg.getSettingsTableModel();
int useDefaultCol = model.findColumn("Use Default"); int useDefaultCol = model.findColumn("Use Default");
int rowCnt = model.getRowCount(); int rowCnt = model.getRowCount();
@ -242,27 +245,25 @@ public abstract class AbstractDataActionTest extends AbstractGhidraHeadedIntegra
waitForSwing(); waitForSwing();
final DataSettingsDialog.SettingsTableModel model = dlg.getSettingsTableModel(); AbstractSortedTableModel<SettingsRowObject> model = dlg.getSettingsTableModel();
final int settingsCol = model.findColumn("Settings");
assertEquals(Settings.class, model.getColumnClass(settingsCol));
error = null; error = null;
Runnable r = () -> { Runnable r = () -> {
int nameCol = model.findColumn("Name"); int nameCol = model.findColumn("Name");
int settingsCol1 = model.findColumn("Settings"); int settingsCol = model.findColumn("Settings");
int rowCnt = model.getRowCount(); int rowCnt = model.getRowCount();
for (int i = 0; i < rowCnt; i++) { for (int i = 0; i < rowCnt; i++) {
String name = (String) model.getValueAt(i, nameCol); String name = (String) model.getValueAt(i, nameCol);
int index = findSettingIndex(settingNames, name); int index = findSettingIndex(settingNames, name);
if (index != -1) { if (index != -1) {
Object v = model.getValueAt(i, settingsCol1); Object v = model.getValueAt(i, settingsCol);
if (v instanceof StringChoices) { if (v instanceof StringChoices) {
StringChoices choices = (StringChoices) v; StringChoices choices = (StringChoices) v;
choices.setSelectedValue(newValues[index]); choices.setSelectedValue(newValues[index]);
model.setValueAt(choices, i, settingsCol1); model.setValueAt(choices, i, settingsCol);
} }
else { else {
error = "Unsupported test setting: " + v.getClass(); error = "Unsupported test setting: " + v.getClass();
@ -770,7 +771,7 @@ public abstract class AbstractDataActionTest extends AbstractGhidraHeadedIntegra
protected void manipulateMutabilitySettings(boolean testDefaultSetting, boolean insideStruct, protected void manipulateMutabilitySettings(boolean testDefaultSetting, boolean insideStruct,
boolean commonStruct, Data data1, Data data2) throws Exception { boolean commonStruct, Data data1, Data data2) throws Exception {
assertTrue(data1.getDataType() == data2.getDataType()); assertSame(data1.getDataType(), data2.getDataType());
boolean settingsAreShared = boolean settingsAreShared =
testDefaultSetting && (!insideStruct || (insideStruct && commonStruct)); testDefaultSetting && (!insideStruct || (insideStruct && commonStruct));

View file

@ -26,14 +26,6 @@ import ghidra.program.model.data.DataType;
@Category(NightlyCategory.class) @Category(NightlyCategory.class)
public class DataAction1Test extends AbstractDataActionTest { public class DataAction1Test extends AbstractDataActionTest {
/**
* Constructor for FallThroughActionTest.
* @param arg0
*/
public DataAction1Test() {
super();
}
@Test @Test
public void testAllStructDataSettings() throws Exception { public void testAllStructDataSettings() throws Exception {

View file

@ -26,14 +26,6 @@ import ghidra.program.model.data.DataType;
@Category(NightlyCategory.class) @Category(NightlyCategory.class)
public class DataAction2Test extends AbstractDataActionTest { public class DataAction2Test extends AbstractDataActionTest {
/**
* Constructor for FallThroughActionTest.
* @param arg0
*/
public DataAction2Test() {
super();
}
@Test @Test
public void testAllDefaultStructDataSettings() throws Exception { public void testAllDefaultStructDataSettings() throws Exception {

View file

@ -26,20 +26,11 @@ import ghidra.program.model.data.DataType;
@Category(NightlyCategory.class) @Category(NightlyCategory.class)
public class DataAction3Test extends AbstractDataActionTest { public class DataAction3Test extends AbstractDataActionTest {
/**
* Constructor for FallThroughActionTest.
* @param arg0
*/
public DataAction3Test() {
super();
}
@Test @Test
public void testAllDefaultDataSettings() throws Exception { public void testAllDefaultDataSettings() throws Exception {
List<DataType> builtIns = getBuiltInDataTypesAsFavorites(); List<DataType> builtIns = getBuiltInDataTypesAsFavorites();
for (DataType type : builtIns) { for (DataType type : builtIns) {
//if (!(type instanceof UnicodeDataType)) continue;
String actionName = "Define " + type.getName(); String actionName = "Define " + type.getName();
manipulateAllSettings(true, false, false, actionName); manipulateAllSettings(true, false, false, actionName);
} }

View file

@ -484,7 +484,7 @@ public class DataAction4Test extends AbstractDataActionTest {
doAction(CREATE_ARRAY, false); doAction(CREATE_ARRAY, false);
final NumberInputDialog dlg1 = env.waitForDialogComponent(NumberInputDialog.class, 1000); final NumberInputDialog dlg1 = waitForDialogComponent(NumberInputDialog.class);
assertNotNull("Expected element count input dialog", dlg1); assertNotNull("Expected element count input dialog", dlg1);
Runnable r = () -> dlg1.setInput(0x20); Runnable r = () -> dlg1.setInput(0x20);
@ -500,8 +500,6 @@ public class DataAction4Test extends AbstractDataActionTest {
// Test action disablement on array element location // Test action disablement on array element location
Data array = getContextData();
Data d0 = array.getComponent(0);
BytesFieldLocation loc = BytesFieldLocation loc =
new BytesFieldLocation(program, addr(0x010069f2), addr(0x010069f2), new int[] { 0 }, 0); new BytesFieldLocation(program, addr(0x010069f2), addr(0x010069f2), new int[] { 0 }, 0);
locationGenerated(loc); locationGenerated(loc);
@ -515,7 +513,7 @@ public class DataAction4Test extends AbstractDataActionTest {
doAction(CREATE_ARRAY, false); doAction(CREATE_ARRAY, false);
final NumberInputDialog dlg2 = env.waitForDialogComponent(NumberInputDialog.class, 1000); final NumberInputDialog dlg2 = waitForDialogComponent(NumberInputDialog.class);
assertNotNull("Expected element count input dialog", dlg2); assertNotNull("Expected element count input dialog", dlg2);
r = () -> dlg2.setInput(0x10); r = () -> dlg2.setInput(0x10);
@ -748,7 +746,7 @@ public class DataAction4Test extends AbstractDataActionTest {
doAction(CREATE_ARRAY, false); doAction(CREATE_ARRAY, false);
final NumberInputDialog dlg1 = env.waitForDialogComponent(NumberInputDialog.class, 1000); final NumberInputDialog dlg1 = waitForDialogComponent(NumberInputDialog.class);
assertNotNull("Expected element count input dialog", dlg1); assertNotNull("Expected element count input dialog", dlg1);
Runnable r = () -> dlg1.setInput(5); Runnable r = () -> dlg1.setInput(5);

View file

@ -794,7 +794,7 @@ public class VTFunctionAssociationProvider extends ComponentProviderAdapter
private void getTableFilterString(String tableName, ThreadedTableModel<?, ?> model, private void getTableFilterString(String tableName, ThreadedTableModel<?, ?> model,
StringBuffer buffy) { StringBuffer buffy) {
int filteredCount = model.getRowCount(); int filteredCount = model.getRowCount();
int unfilteredCount = model.getUnfilteredCount(); int unfilteredCount = model.getUnfilteredRowCount();
buffy.append(tableName).append(" - ").append(filteredCount).append(" functions"); buffy.append(tableName).append(" - ").append(filteredCount).append(" functions");
if (filteredCount != unfilteredCount) { if (filteredCount != unfilteredCount) {

View file

@ -263,7 +263,7 @@ public class VTImpliedMatchesTableProvider extends ComponentProviderAdapter
impliedMatchTableModel.addTableModelListener(e -> { impliedMatchTableModel.addTableModelListener(e -> {
int filteredCount = impliedMatchTableModel.getRowCount(); int filteredCount = impliedMatchTableModel.getRowCount();
int unfilteredCount = impliedMatchTableModel.getUnfilteredCount(); int unfilteredCount = impliedMatchTableModel.getUnfilteredRowCount();
String sessionName = controller.getVersionTrackingSessionName(); String sessionName = controller.getVersionTrackingSessionName();
StringBuffer buffy = new StringBuffer(); StringBuffer buffy = new StringBuffer();

View file

@ -177,7 +177,7 @@ public class VTMarkupItemsTableModel extends AddressBasedTableModel<VTMarkupItem
// Inner Classes // Inner Classes
//================================================================================================== //==================================================================================================
private class MarkupTablePassthroughFilter implements TableFilter<VTMarkupItem> { private static class MarkupTablePassthroughFilter implements TableFilter<VTMarkupItem> {
private List<Filter<VTMarkupItem>> appliedFilters; private List<Filter<VTMarkupItem>> appliedFilters;
@ -243,6 +243,20 @@ public class VTMarkupItemsTableModel extends AddressBasedTableModel<VTMarkupItem
} }
return true; return true;
} }
@Override
public int hashCode() {
// not meant to put in hashing structures; the data for equals changes
throw new UnsupportedOperationException();
}
@Override
public boolean equals(Object obj) {
// For now we don't support equals(); if this filter gets re-created,
// then the table must be re-filtered. If we decide to implement this method, then
// we must also implement equals() on the filters used by this filter.
return this == obj;
}
} }
// column for selecting/editing? // column for selecting/editing?
@ -478,7 +492,7 @@ public class VTMarkupItemsTableModel extends AddressBasedTableModel<VTMarkupItem
private static final String NO_SOURCE_TEXT = "None"; private static final String NO_SOURCE_TEXT = "None";
private GColumnRenderer<String> sourceCellRenderer = new AbstractGColumnRenderer<String>() { private GColumnRenderer<String> sourceCellRenderer = new AbstractGColumnRenderer<>() {
@Override @Override
public Component getTableCellRendererComponent(GTableCellRenderingData data) { public Component getTableCellRendererComponent(GTableCellRenderingData data) {
@ -543,30 +557,28 @@ public class VTMarkupItemsTableModel extends AddressBasedTableModel<VTMarkupItem
static class IsInDBTableColumn static class IsInDBTableColumn
extends AbstractProgramBasedDynamicTableColumn<VTMarkupItem, Boolean> { extends AbstractProgramBasedDynamicTableColumn<VTMarkupItem, Boolean> {
private GColumnRenderer<Boolean> isInDBCellRenderer = private GColumnRenderer<Boolean> isInDBCellRenderer = new AbstractGColumnRenderer<>() {
new AbstractGColumnRenderer<Boolean>() { @Override
@Override public Component getTableCellRendererComponent(GTableCellRenderingData data) {
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
Object value = data.getValue(); Object value = data.getValue();
boolean isInDB = ((Boolean) value).booleanValue(); boolean isInDB = ((Boolean) value).booleanValue();
GTableCellRenderingData renderData = GTableCellRenderingData renderData = data.copyWithNewValue(isInDB ? "yes" : null);
data.copyWithNewValue(isInDB ? "yes" : null);
JLabel renderer = (JLabel) super.getTableCellRendererComponent(renderData); JLabel renderer = (JLabel) super.getTableCellRendererComponent(renderData);
renderer.setOpaque(true); renderer.setOpaque(true);
return renderer; return renderer;
} }
@Override @Override
public String getFilterString(Boolean t, Settings settings) { public String getFilterString(Boolean t, Settings settings) {
boolean isInDB = t.booleanValue(); boolean isInDB = t.booleanValue();
return isInDB ? "yes" : ""; return isInDB ? "yes" : "";
} }
}; };
@Override @Override
public String getColumnName() { public String getColumnName() {

View file

@ -321,14 +321,14 @@ public class VTMarkupItemsTableProvider extends ComponentProviderAdapter
markupItemsTableModel = new VTMarkupItemsTableModel(controller); markupItemsTableModel = new VTMarkupItemsTableModel(controller);
markupItemsTableModel.addTableModelListener(e -> { markupItemsTableModel.addTableModelListener(e -> {
int filteredCount = markupItemsTableModel.getRowCount(); int filteredCount = markupItemsTableModel.getRowCount();
int unfilteredCount = markupItemsTableModel.getUnfilteredCount(); int unfilteredCount = markupItemsTableModel.getUnfilteredRowCount();
String sessionName = controller.getVersionTrackingSessionName(); String sessionName = controller.getVersionTrackingSessionName();
StringBuffer buffy = new StringBuffer(); StringBuffer buffy = new StringBuffer();
buffy.append("[Session: ").append(sessionName).append("] "); buffy.append("[Session: ").append(sessionName).append("] ");
buffy.append('-').append(markupItemsTableModel.getRowCount()).append(" markup items"); buffy.append('-').append(markupItemsTableModel.getRowCount()).append(" markup items");
if (filteredCount != unfilteredCount) { if (filteredCount != unfilteredCount) {
buffy.append(" (of ").append(markupItemsTableModel.getUnfilteredCount()).append( buffy.append(" (of ").append(markupItemsTableModel.getUnfilteredRowCount()).append(
')'); ')');
} }
@ -848,7 +848,7 @@ public class VTMarkupItemsTableProvider extends ComponentProviderAdapter
if (filtered) { if (filtered) {
int filteredCount = markupItemsTableModel.getRowCount(); int filteredCount = markupItemsTableModel.getRowCount();
int unfilteredCount = markupItemsTableModel.getUnfilteredCount(); int unfilteredCount = markupItemsTableModel.getUnfilteredRowCount();
int filteredOutCount = unfilteredCount - filteredCount; int filteredOutCount = unfilteredCount - filteredCount;
ancillaryFilterButton.setToolTipText( ancillaryFilterButton.setToolTipText(
"More Filters - " + filteredOutCount + " item(s) hidden"); "More Filters - " + filteredOutCount + " item(s) hidden");

View file

@ -181,7 +181,7 @@ public class VTMatchTableProvider extends ComponentProviderAdapter
if (filtered) { if (filtered) {
int filteredCount = matchesTableModel.getRowCount(); int filteredCount = matchesTableModel.getRowCount();
int unfilteredCount = matchesTableModel.getUnfilteredCount(); int unfilteredCount = matchesTableModel.getUnfilteredRowCount();
int filteredOutCount = unfilteredCount - filteredCount; int filteredOutCount = unfilteredCount - filteredCount;
ancillaryFilterButton.setToolTipText( ancillaryFilterButton.setToolTipText(
"More Filters - " + filteredOutCount + " item(s) hidden"); "More Filters - " + filteredOutCount + " item(s) hidden");
@ -231,7 +231,7 @@ public class VTMatchTableProvider extends ComponentProviderAdapter
matchesTableModel = new VTMatchTableModel(controller); matchesTableModel = new VTMatchTableModel(controller);
matchesTableModel.addTableModelListener(e -> { matchesTableModel.addTableModelListener(e -> {
int filteredCount = matchesTableModel.getRowCount(); int filteredCount = matchesTableModel.getRowCount();
int unfilteredCount = matchesTableModel.getUnfilteredCount(); int unfilteredCount = matchesTableModel.getUnfilteredRowCount();
String sessionName = controller.getVersionTrackingSessionName(); String sessionName = controller.getVersionTrackingSessionName();
StringBuffer buffy = new StringBuffer(); StringBuffer buffy = new StringBuffer();

View file

@ -137,7 +137,7 @@ public abstract class AbstractVTMatchTableModel extends AddressBasedTableModel<V
//================================================================================================== //==================================================================================================
// Inner Classes // Inner Classes
//================================================================================================== //==================================================================================================
private class MatchTablePassthroughFilter implements TableFilter<VTMatch> { private static class MatchTablePassthroughFilter implements TableFilter<VTMatch> {
private List<Filter<VTMatch>> appliedFilters; private List<Filter<VTMatch>> appliedFilters;
@ -203,6 +203,20 @@ public abstract class AbstractVTMatchTableModel extends AddressBasedTableModel<V
return true; return true;
} }
@Override
public int hashCode() {
// not meant to put in hashing structures; the data for equals changes
throw new UnsupportedOperationException();
}
@Override
public boolean equals(Object obj) {
// For now we don't support equals(); if this filter gets re-created,
// then the table must be re-filtered. If we decide to implement this method, then
// we must also implement equals() on the filters used by this filter.
return this == obj;
}
} }
static class MarkupStatusColumnComparator implements Comparator<VTMatch> { static class MarkupStatusColumnComparator implements Comparator<VTMatch> {
@ -397,7 +411,7 @@ public abstract class AbstractVTMatchTableModel extends AddressBasedTableModel<V
return 55; return 55;
} }
private GColumnRenderer<VTScore> renderer = new AbstractGColumnRenderer<VTScore>() { private GColumnRenderer<VTScore> renderer = new AbstractGColumnRenderer<>() {
@Override @Override
public Component getTableCellRendererComponent(GTableCellRenderingData data) { public Component getTableCellRendererComponent(GTableCellRenderingData data) {
@ -457,7 +471,7 @@ public abstract class AbstractVTMatchTableModel extends AddressBasedTableModel<V
return 55; return 55;
} }
private GColumnRenderer<VTScore> renderer = new AbstractGColumnRenderer<VTScore>() { private GColumnRenderer<VTScore> renderer = new AbstractGColumnRenderer<>() {
@Override @Override
public Component getTableCellRendererComponent(GTableCellRenderingData data) { public Component getTableCellRendererComponent(GTableCellRenderingData data) {
@ -550,7 +564,7 @@ public abstract class AbstractVTMatchTableModel extends AddressBasedTableModel<V
} }
private GColumnRenderer<DisplayableLabel> labelCellRenderer = private GColumnRenderer<DisplayableLabel> labelCellRenderer =
new AbstractGColumnRenderer<DisplayableLabel>() { new AbstractGColumnRenderer<>() {
@Override @Override
public Component getTableCellRendererComponent(GTableCellRenderingData data) { public Component getTableCellRendererComponent(GTableCellRenderingData data) {
@ -681,7 +695,7 @@ public abstract class AbstractVTMatchTableModel extends AddressBasedTableModel<V
} }
private GColumnRenderer<DisplayableAddress> addressCellRenderer = private GColumnRenderer<DisplayableAddress> addressCellRenderer =
new AbstractGColumnRenderer<DisplayableAddress>() { new AbstractGColumnRenderer<>() {
@Override @Override
public Component getTableCellRendererComponent(GTableCellRenderingData data) { public Component getTableCellRendererComponent(GTableCellRenderingData data) {
@ -788,7 +802,7 @@ public abstract class AbstractVTMatchTableModel extends AddressBasedTableModel<V
} }
private GColumnRenderer<DisplayableLabel> labelCellRenderer = private GColumnRenderer<DisplayableLabel> labelCellRenderer =
new AbstractGColumnRenderer<DisplayableLabel>() { new AbstractGColumnRenderer<>() {
@Override @Override
public Component getTableCellRendererComponent(GTableCellRenderingData data) { public Component getTableCellRendererComponent(GTableCellRenderingData data) {
@ -921,7 +935,7 @@ public abstract class AbstractVTMatchTableModel extends AddressBasedTableModel<V
} }
private GColumnRenderer<DisplayableAddress> addressCellRenderer = private GColumnRenderer<DisplayableAddress> addressCellRenderer =
new AbstractGColumnRenderer<DisplayableAddress>() { new AbstractGColumnRenderer<>() {
@Override @Override
public Component getTableCellRendererComponent(GTableCellRenderingData data) { public Component getTableCellRendererComponent(GTableCellRenderingData data) {

View file

@ -16,18 +16,21 @@
package docking.widgets.dialogs; package docking.widgets.dialogs;
import java.awt.*; import java.awt.*;
import java.util.ArrayList;
import java.util.List;
import javax.swing.*; import javax.swing.*;
import javax.swing.border.EmptyBorder; import javax.swing.border.EmptyBorder;
import javax.swing.event.PopupMenuEvent; import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener; import javax.swing.event.PopupMenuListener;
import javax.swing.table.*; import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableCellEditor;
import docking.DialogComponentProvider; import docking.DialogComponentProvider;
import docking.DockingWindowManager; import docking.DockingWindowManager;
import docking.widgets.checkbox.GCheckBox; import docking.widgets.checkbox.GCheckBox;
import docking.widgets.combobox.GComboBox; import docking.widgets.combobox.GComboBox;
import docking.widgets.table.DefaultSortedTableModel; import docking.widgets.table.AbstractSortedTableModel;
import docking.widgets.table.GTable; import docking.widgets.table.GTable;
import ghidra.docking.settings.*; import ghidra.docking.settings.*;
import ghidra.util.HelpLocation; import ghidra.util.HelpLocation;
@ -44,9 +47,6 @@ public class SettingsDialog extends DialogComponentProvider {
private SettingsTableModel settingsTableModel; private SettingsTableModel settingsTableModel;
private GTable settingsTable; private GTable settingsTable;
/**
* Construct instance settings dialog.
*/
public SettingsDialog(HelpLocation help) { public SettingsDialog(HelpLocation help) {
super("Settings", true, false, true, false); super("Settings", true, false, true, false);
if (help != null) { if (help != null) {
@ -58,9 +58,6 @@ public class SettingsDialog extends DialogComponentProvider {
setHelpLocation(new HelpLocation("Tables/GhidraTableHeaders.html", "ColumnSettings")); setHelpLocation(new HelpLocation("Tables/GhidraTableHeaders.html", "ColumnSettings"));
} }
/**
* Show dialog for the specified set of settings definitions and settings storage.
*/
public void show(Component parent, String title, SettingsDefinition[] newSettingsDefs, public void show(Component parent, String title, SettingsDefinition[] newSettingsDefs,
Settings newSettings) { Settings newSettings) {
this.settingsDefs = newSettingsDefs; this.settingsDefs = newSettingsDefs;
@ -82,18 +79,13 @@ public class SettingsDialog extends DialogComponentProvider {
JPanel workPanel = new JPanel(new BorderLayout()); JPanel workPanel = new JPanel(new BorderLayout());
workPanel.setBorder(new EmptyBorder(10, 10, 10, 10)); workPanel.setBorder(new EmptyBorder(10, 10, 10, 10));
settingsTableModel = new SettingsTableModel(); settingsTableModel = new SettingsTableModel(settingsDefs);
settingsTable = new GTable(settingsTableModel);
DefaultSortedTableModel sorter = new DefaultSortedTableModel(settingsTableModel);
sorter.sortByColumn(SettingsTableModel.DEFAULT_SORT_COL);
settingsTable = new GTable(sorter);
settingsTable.setAutoscrolls(true); settingsTable.setAutoscrolls(true);
settingsTable.setRowSelectionAllowed(false); settingsTable.setRowSelectionAllowed(false);
settingsTable.setColumnSelectionAllowed(false); settingsTable.setColumnSelectionAllowed(false);
// disable user sorting and column adding (we don't expect enough data to require sort // disable sorting and column adding (we don't expect enough data to require sort changes)
// changes)
settingsTable.getTableHeader().setReorderingAllowed(false); settingsTable.getTableHeader().setReorderingAllowed(false);
settingsTable.setColumnHeaderPopupEnabled(false); settingsTable.setColumnHeaderPopupEnabled(false);
settingsTable.setUserSortingEnabled(false); settingsTable.setUserSortingEnabled(false);
@ -109,83 +101,97 @@ public class SettingsDialog extends DialogComponentProvider {
return workPanel; return workPanel;
} }
/**
* @see ghidra.util.bean.GhidraDialog#cancelCallback()
*/
@Override @Override
protected void cancelCallback() { protected void cancelCallback() {
dispose(); dispose();
} }
class SettingsTableModel extends AbstractTableModel { //==================================================================================================
// Private Methods
//==================================================================================================
static final int DEFAULT_SORT_COL = 0; private class SettingsRowObject {
private SettingsDefinition definition;
SettingsRowObject(SettingsDefinition definition) {
this.definition = definition;
}
public String getName() {
return definition.getName();
}
Object getSettingsChoices() {
if (definition instanceof EnumSettingsDefinition) {
EnumSettingsDefinition def = (EnumSettingsDefinition) definition;
StringChoices choices = new StringChoices(def.getDisplayChoices(settings));
choices.setSelectedValue(def.getChoice(settings));
return choices;
}
else if (definition instanceof BooleanSettingsDefinition) {
BooleanSettingsDefinition def = (BooleanSettingsDefinition) definition;
return Boolean.valueOf(def.getValue(settings));
}
return "<Unsupported>";
}
boolean setSettingsChoice(Object value) {
if (definition instanceof EnumSettingsDefinition) {
EnumSettingsDefinition def = (EnumSettingsDefinition) definition;
StringChoices choices = (StringChoices) value;
def.setChoice(settings, choices.getSelectedValueIndex());
return true;
}
else if (definition instanceof BooleanSettingsDefinition) {
BooleanSettingsDefinition def = (BooleanSettingsDefinition) definition;
def.setValue(settings, ((Boolean) value).booleanValue());
return true;
}
return false;
}
void clear(Settings s) {
definition.clear(s);
}
}
private class SettingsTableModel extends AbstractSortedTableModel<SettingsRowObject> {
private List<SettingsRowObject> rows = new ArrayList<>();
SettingsTableModel(SettingsDefinition[] settingsDefs) {
for (SettingsDefinition sd : settingsDefs) {
rows.add(new SettingsRowObject(sd));
}
}
@Override
public List<SettingsRowObject> getModelData() {
return rows;
}
@Override
public String getName() {
return "Settings Definition Model";
}
@Override
public boolean isSortable(int columnIndex) {
return columnIndex == 0;
}
@Override
public boolean isCellEditable(int row, int col) {
return col != 0;
}
@Override @Override
public int getColumnCount() { public int getColumnCount() {
return 2; return 2;
} }
@Override
public int getRowCount() {
return settingsDefs != null ? settingsDefs.length : 0;
}
@Override
public Object getValueAt(int row, int col) {
switch (col) {
case 0:
return settingsDefs[row].getName();
case 1:
if (settingsDefs[row] instanceof EnumSettingsDefinition) {
EnumSettingsDefinition def = (EnumSettingsDefinition) settingsDefs[row];
StringChoices choices = new StringChoices(def.getDisplayChoices(settings));
choices.setSelectedValue(def.getChoice(settings));
return choices;
}
else if (settingsDefs[row] instanceof BooleanSettingsDefinition) {
BooleanSettingsDefinition def =
(BooleanSettingsDefinition) settingsDefs[row];
return new Boolean(def.getValue(settings));
}
return "<Unsupported>";
}
return null;
}
/**
* @see TableModel#setValueAt(Object, int, int)
*/
@Override
public void setValueAt(Object value, int row, int col) {
switch (col) {
case 1:
if (settingsDefs[row] instanceof EnumSettingsDefinition) {
EnumSettingsDefinition def = (EnumSettingsDefinition) settingsDefs[row];
StringChoices choices = (StringChoices) value;
def.setChoice(settings, choices.getSelectedValueIndex());
fireTableDataChanged();
}
else if (settingsDefs[row] instanceof BooleanSettingsDefinition) {
BooleanSettingsDefinition def =
(BooleanSettingsDefinition) settingsDefs[row];
def.setValue(settings, ((Boolean) value).booleanValue());
fireTableDataChanged();
}
break;
case 2:
if (((Boolean) value).booleanValue()) {
settingsDefs[row].clear(settings);
fireTableDataChanged();
}
break;
}
}
/**
* @see TableModel#getColumnName(int)
*/
@Override @Override
public String getColumnName(int col) { public String getColumnName(int col) {
switch (col) { switch (col) {
@ -197,32 +203,34 @@ public class SettingsDialog extends DialogComponentProvider {
return null; return null;
} }
/**
* @see TableModel#getColumnClass(int)
*/
@Override @Override
public Class<?> getColumnClass(int col) { public Object getColumnValueForRow(SettingsRowObject t, int columnIndex) {
switch (col) { switch (columnIndex) {
case 0: case 0:
return String.class; return t.getName();
case 1: case 1:
return Settings.class; return t.getSettingsChoices();
} }
return null; return null;
} }
public boolean isColumnSortable(int col) {
return col == 0;
}
/**
* @see TableModel#isCellEditable(int, int)
*/
@Override @Override
public boolean isCellEditable(int row, int col) { public void setValueAt(Object value, int row, int col) {
return col != 0; SettingsRowObject rowObject = rows.get(row);
switch (col) {
case 1:
if (rowObject.setSettingsChoice(value)) {
fireTableDataChanged();
}
break;
case 2:
if (((Boolean) value).booleanValue()) {
rowObject.clear(settings);
fireTableDataChanged();
}
break;
}
} }
} }
private class SettingsEditor extends AbstractCellEditor private class SettingsEditor extends AbstractCellEditor
@ -235,28 +243,20 @@ public class SettingsDialog extends DialogComponentProvider {
private GComboBox<String> comboBox = new GComboBox<>(); private GComboBox<String> comboBox = new GComboBox<>();
private GCheckBox checkBox = new GCheckBox(); private GCheckBox checkBox = new GCheckBox();
private final Runnable editStopped = new Runnable() { private final Runnable editStopped = () -> fireEditingStopped();
@Override
public void run() {
fireEditingStopped();
}
};
SettingsEditor() { SettingsEditor() {
super(); super();
comboBox.addPopupMenuListener(this); comboBox.addPopupMenuListener(this);
} }
/**
* @see javax.swing.CellEditor#getCellEditorValue()
*/
@Override @Override
public Object getCellEditorValue() { public Object getCellEditorValue() {
switch (mode) { switch (mode) {
case ENUM: case ENUM:
return getComboBoxEnum(); return getComboBoxEnum();
case BOOLEAN: case BOOLEAN:
return Boolean.valueOf(checkBox.isSelected()); return checkBox.isSelected();
} }
throw new AssertException(); throw new AssertException();
} }
@ -271,9 +271,6 @@ public class SettingsDialog extends DialogComponentProvider {
return choices; return choices;
} }
/**
* @see javax.swing.table.TableCellEditor#getTableCellEditorComponent(javax.swing.JTable, java.lang.Object, boolean, int, int)
*/
@Override @Override
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected,
int row, int column) { int row, int column) {
@ -304,26 +301,19 @@ public class SettingsDialog extends DialogComponentProvider {
comboBox.setSelectedIndex(choices.getSelectedValueIndex()); comboBox.setSelectedIndex(choices.getSelectedValueIndex());
} }
/**
* @see javax.swing.event.PopupMenuListener#popupMenuWillBecomeVisible(javax.swing.event.PopupMenuEvent)
*/
@Override @Override
public void popupMenuWillBecomeVisible(PopupMenuEvent e) { public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
// stub
} }
/**
* @see javax.swing.event.PopupMenuListener#popupMenuWillBecomeInvisible(javax.swing.event.PopupMenuEvent)
*/
@Override @Override
public void popupMenuWillBecomeInvisible(PopupMenuEvent e) { public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
SwingUtilities.invokeLater(editStopped); SwingUtilities.invokeLater(editStopped);
} }
/**
* @see javax.swing.event.PopupMenuListener#popupMenuCanceled(javax.swing.event.PopupMenuEvent)
*/
@Override @Override
public void popupMenuCanceled(PopupMenuEvent e) { public void popupMenuCanceled(PopupMenuEvent e) {
// stub
} }
} }

View file

@ -15,12 +15,13 @@
*/ */
package docking.widgets.filter; package docking.widgets.filter;
import java.util.Objects;
import java.util.regex.Pattern; import java.util.regex.Pattern;
public abstract class AbstractPatternTextFilter implements TextFilter { public abstract class AbstractPatternTextFilter implements TextFilter {
protected final String filterText; protected final String filterText;
private Pattern filterPattern; protected Pattern filterPattern;
protected AbstractPatternTextFilter(String filterText) { protected AbstractPatternTextFilter(String filterText) {
this.filterText = filterText; this.filterText = filterText;
@ -67,6 +68,56 @@ public abstract class AbstractPatternTextFilter implements TextFilter {
return filterPattern; return filterPattern;
} }
@Override
public int hashCode() {
// not meant to put in hashing structures; the data for equals may change over time
throw new UnsupportedOperationException();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
AbstractPatternTextFilter other = (AbstractPatternTextFilter) obj;
if (!patternsEqual(createPattern(), other.createPattern())) {
return false;
}
if (!Objects.equals(filterText, other.filterText)) {
return false;
}
return true;
}
private boolean patternsEqual(Pattern p1, Pattern p2) {
String myPattern = getPatternString(p1);
String otherPattern = getPatternString(p2);
if (!myPattern.equals(otherPattern)) {
return false;
}
int f1 = getPatternFlags(p1);
int f2 = getPatternFlags(p2);
return f1 == f2;
}
private String getPatternString(Pattern p) {
return p == null ? "" : p.pattern();
}
private int getPatternFlags(Pattern p) {
return p == null ? -1 : p.flags();
}
@Override @Override
public String toString() { public String toString() {
//@formatter:off //@formatter:off

View file

@ -24,13 +24,8 @@ import ghidra.util.UserSearchUtils;
*/ */
public class ContainsTextFilter extends MatchesPatternTextFilter { public class ContainsTextFilter extends MatchesPatternTextFilter {
private boolean caseSensitive;
private boolean allowGlobbing;
public ContainsTextFilter(String filterText, boolean caseSensitive, boolean allowGlobbing) { public ContainsTextFilter(String filterText, boolean caseSensitive, boolean allowGlobbing) {
super(filterText); super(filterText, caseSensitive, allowGlobbing);
this.caseSensitive = caseSensitive;
this.allowGlobbing = allowGlobbing;
} }
@Override @Override

View file

@ -15,6 +15,8 @@
*/ */
package docking.widgets.filter; package docking.widgets.filter;
import java.util.Objects;
public class InvertedTextFilter implements TextFilter { public class InvertedTextFilter implements TextFilter {
private final TextFilter filter; private final TextFilter filter;
@ -39,4 +41,28 @@ public class InvertedTextFilter implements TextFilter {
return filter.getFilterText(); return filter.getFilterText();
} }
@Override
public int hashCode() {
// not meant to put in hashing structures; the data for equals may change over time
throw new UnsupportedOperationException();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
InvertedTextFilter other = (InvertedTextFilter) obj;
if (!Objects.equals(filter, other.filter)) {
return false;
}
return true;
}
} }

View file

@ -24,14 +24,9 @@ import ghidra.util.UserSearchUtils;
*/ */
public class MatchesExactlyTextFilter extends MatchesPatternTextFilter { public class MatchesExactlyTextFilter extends MatchesPatternTextFilter {
private boolean caseSensitive;
private boolean allowGlobbing;
public MatchesExactlyTextFilter(String filterText, boolean caseSensitive, public MatchesExactlyTextFilter(String filterText, boolean caseSensitive,
boolean allowGlobbing) { boolean allowGlobbing) {
super(filterText); super(filterText, caseSensitive, allowGlobbing);
this.caseSensitive = caseSensitive;
this.allowGlobbing = allowGlobbing;
} }
@Override @Override

View file

@ -22,12 +22,52 @@ import java.util.regex.Pattern;
*/ */
public abstract class MatchesPatternTextFilter extends AbstractPatternTextFilter { public abstract class MatchesPatternTextFilter extends AbstractPatternTextFilter {
public MatchesPatternTextFilter(String filterText) { protected boolean caseSensitive;
protected boolean allowGlobbing;
public MatchesPatternTextFilter(String filterText, boolean caseSensitive,
boolean allowGlobbing) {
super(filterText); super(filterText);
this.caseSensitive = caseSensitive;
this.allowGlobbing = allowGlobbing;
} }
@Override @Override
public boolean matches(String text, Pattern pattern) { public boolean matches(String text, Pattern pattern) {
return pattern.matcher(text).matches(); return pattern.matcher(text).matches();
} }
@Override
public int hashCode() {
// not meant to put in hashing structures; the data for equals may change over time
throw new UnsupportedOperationException();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
if (!super.equals(obj)) {
return false;
}
MatchesPatternTextFilter other = (MatchesPatternTextFilter) obj;
if (allowGlobbing != other.allowGlobbing) {
return false;
}
if (caseSensitive != other.caseSensitive) {
return false;
}
return true;
}
} }

View file

@ -24,13 +24,8 @@ import ghidra.util.UserSearchUtils;
*/ */
public class StartsWithTextFilter extends MatchesPatternTextFilter { public class StartsWithTextFilter extends MatchesPatternTextFilter {
private boolean caseSensitive;
private boolean allowGlobbing;
public StartsWithTextFilter(String filterText, boolean caseSensitive, boolean allowGlobbing) { public StartsWithTextFilter(String filterText, boolean caseSensitive, boolean allowGlobbing) {
super(filterText); super(filterText, caseSensitive, allowGlobbing);
this.caseSensitive = caseSensitive;
this.allowGlobbing = allowGlobbing;
} }
@Override @Override

View file

@ -93,13 +93,13 @@ public abstract class AbstractGTableModel<T> extends AbstractTableModel
} }
/** /**
* A convenience method for subclasses to quickly/efficiently search for the index of a given * A convenience method to search for the index of a given
* row object <b>that is visible in the GUI</b>. The <i>visible</i> limitation is due to the * row object <b>that is visible in the GUI</b>. The <i>visible</i> limitation is due to the
* fact that the data searched is retrieved from {@link #getModelData()}, which may be * fact that the data searched is retrieved from {@link #getModelData()}, which may be
* filtered. * filtered.
* <p> *
* If a need for access to all of the data is required in the future, then an overloaded * <p>Note: this operation is O(n). For quick lookups, consider using the sorted version
* version of this method should be created that takes the data to be searched. * of this class.
* *
* @param rowObject The object for which to search. * @param rowObject The object for which to search.
* @return the index of the item in the data returned by * @return the index of the item in the data returned by
@ -108,6 +108,14 @@ public abstract class AbstractGTableModel<T> extends AbstractTableModel
return getIndexForRowObject(rowObject, getModelData()); return getIndexForRowObject(rowObject, getModelData());
} }
/**
* Returns the index for the given object in the given list; -1 when the item is not in
* the list.
*
* @param rowObject the item
* @param data the data
* @return the index
*/
protected int getIndexForRowObject(T rowObject, List<T> data) { protected int getIndexForRowObject(T rowObject, List<T> data) {
return data.indexOf(rowObject); return data.indexOf(rowObject);
} }

View file

@ -17,10 +17,10 @@ package docking.widgets.table;
import java.util.*; import java.util.*;
import javax.swing.SwingUtilities;
import javax.swing.event.TableModelEvent; import javax.swing.event.TableModelEvent;
import javax.swing.table.TableModel; import javax.swing.table.TableModel;
import ghidra.util.SystemUtilities;
import ghidra.util.datastruct.WeakDataStructureFactory; import ghidra.util.datastruct.WeakDataStructureFactory;
import ghidra.util.datastruct.WeakSet; import ghidra.util.datastruct.WeakSet;
@ -31,15 +31,21 @@ import ghidra.util.datastruct.WeakSet;
* <p> * <p>
* In order to define custom comparators for a column, simply override * In order to define custom comparators for a column, simply override
* {@link #createSortComparator(int)}. Otherwise, a default comparator will be created for you. * {@link #createSortComparator(int)}. Otherwise, a default comparator will be created for you.
*
* <p>Note on sorting: it is possible that the user can disable sorting by de-selecting all
* sorted columns. This can also be achieved programmatically by calling
* {@link #setTableSortState(TableSortState)} with a value of
* {@link TableSortState#createUnsortedSortState()}.
* *
* @param <T> The row type upon which the table is based * @param <T> The row type upon which the table is based
*/ */
public abstract class AbstractSortedTableModel<T> extends AbstractGTableModel<T> public abstract class AbstractSortedTableModel<T> extends AbstractGTableModel<T>
implements SortedTableModel { implements SortedTableModel {
private static final long serialVersionUID = 1L;
private final Comparator<T> NO_SORT_COMPARATOR = (o1, o2) -> 0;
private TableSortState pendingSortState; private TableSortState pendingSortState;
private TableSortState sortState; private TableSortState sortState = TableSortState.createUnsortedSortState();
private boolean isSortPending; private boolean isSortPending;
protected boolean hasEverSorted; protected boolean hasEverSorted;
@ -58,6 +64,10 @@ public abstract class AbstractSortedTableModel<T> extends AbstractGTableModel<T>
protected void setDefaultTableSortState(TableSortState defaultSortState) { protected void setDefaultTableSortState(TableSortState defaultSortState) {
sortState = defaultSortState; sortState = defaultSortState;
if (sortState == null) {
sortState = TableSortState.createUnsortedSortState();
}
} }
@Override @Override
@ -81,14 +91,17 @@ public abstract class AbstractSortedTableModel<T> extends AbstractGTableModel<T>
/** /**
* Returns the index of the given row object in this model; -1 if the model does not contain * Returns the index of the given row object in this model; -1 if the model does not contain
* the given object. * the given object.
*
* <p>Warning: if the this model has no sort applied, then performance will be O(n). If
* sorted, then performance is O(log n). You can call {@link #isSorted()} to know when
* this will happen.
*/ */
@Override @Override
public int getRowIndex(T rowObject) { public int getRowIndex(T rowObject) {
if (rowObject == null) { if (rowObject == null) {
return -1; return -1;
} }
return getIndexForRowObject(rowObject); return getIndexForRowObject(rowObject);
} }
@ -121,7 +134,7 @@ public abstract class AbstractSortedTableModel<T> extends AbstractGTableModel<T>
} }
@Override @Override
public void setTableSortState(final TableSortState newSortState) { public void setTableSortState(TableSortState newSortState) {
if (!isValidSortState(newSortState)) { if (!isValidSortState(newSortState)) {
// if the user calls this method with an invalid value, then let them know! // if the user calls this method with an invalid value, then let them know!
@ -165,7 +178,8 @@ public abstract class AbstractSortedTableModel<T> extends AbstractGTableModel<T>
isSortPending = true; isSortPending = true;
pendingSortState = newSortState; pendingSortState = newSortState;
SwingUtilities.invokeLater(() -> sort(getModelData(), createSortingContext(newSortState))); SystemUtilities.runSwingLater(
() -> sort(getModelData(), createSortingContext(newSortState)));
} }
public TableSortState getPendingSortState() { public TableSortState getPendingSortState() {
@ -176,6 +190,10 @@ public abstract class AbstractSortedTableModel<T> extends AbstractGTableModel<T>
return isSortPending; return isSortPending;
} }
public boolean isSorted() {
return !isSortPending && !sortState.isUnsorted();
}
protected TableSortingContext<T> createSortingContext(TableSortState newSortState) { protected TableSortingContext<T> createSortingContext(TableSortState newSortState) {
return new TableSortingContext<>(newSortState, getComparatorChain(newSortState)); return new TableSortingContext<>(newSortState, getComparatorChain(newSortState));
} }
@ -205,7 +223,7 @@ public abstract class AbstractSortedTableModel<T> extends AbstractGTableModel<T>
hasEverSorted = true; hasEverSorted = true;
isSortPending = true; isSortPending = true;
pendingSortState = sortState; pendingSortState = sortState;
SwingUtilities.invokeLater(() -> sort(getModelData(), createSortingContext(sortState))); SystemUtilities.runSwingLater(() -> sort(getModelData(), createSortingContext(sortState)));
} }
/** /**
@ -213,9 +231,6 @@ public abstract class AbstractSortedTableModel<T> extends AbstractGTableModel<T>
* row object <b>that is visible in the GUI</b>. The <i>visible</i> limitation is due to the * row object <b>that is visible in the GUI</b>. The <i>visible</i> limitation is due to the
* fact that the data searched is retrieved from {@link #getModelData()}, which may be * fact that the data searched is retrieved from {@link #getModelData()}, which may be
* filtered. * filtered.
* <p>
* If a need for access to all of the data is required in the future, then an overloaded
* version of this method should be created that takes the data to be searched.
* *
* @param rowObject The object for which to search. * @param rowObject The object for which to search.
* @return the index of the item in the data returned by * @return the index of the item in the data returned by
@ -225,10 +240,27 @@ public abstract class AbstractSortedTableModel<T> extends AbstractGTableModel<T>
return getIndexForRowObject(rowObject, getModelData()); return getIndexForRowObject(rowObject, getModelData());
} }
/**
* Returns the index for the given object in the given list
*
* @param rowObject the item
* @param data the data
* @return the index
*/
@Override @Override
protected int getIndexForRowObject(T rowObject, List<T> data) { protected int getIndexForRowObject(T rowObject, List<T> data) {
Comparator<T> comparator = getComparatorChain(sortState); if (isSorted()) {
return Collections.binarySearch(data, rowObject, comparator); Comparator<T> comparator = getComparatorChain(sortState);
return Collections.binarySearch(data, rowObject, comparator);
}
for (int i = 0; i < data.size(); i++) {
T t = data.get(i);
if (rowObject.equals(t)) {
return i;
}
}
return -1;
} }
/** /**
@ -242,6 +274,14 @@ public abstract class AbstractSortedTableModel<T> extends AbstractGTableModel<T>
* comparator for sorting, etc...). * comparator for sorting, etc...).
*/ */
protected void sort(List<T> data, TableSortingContext<T> sortingContext) { protected void sort(List<T> data, TableSortingContext<T> sortingContext) {
if (sortingContext.isUnsorted()) {
// this is the 'no sort' state
sortCompleted(sortingContext);
notifyModelSorted(false);
return;
}
hasEverSorted = true; // signal that we have sorted at least one time hasEverSorted = true; // signal that we have sorted at least one time
Collections.sort(data, sortingContext.getComparator()); Collections.sort(data, sortingContext.getComparator());
sortCompleted(sortingContext); sortCompleted(sortingContext);
@ -315,6 +355,11 @@ public abstract class AbstractSortedTableModel<T> extends AbstractGTableModel<T>
* data is sorted). * data is sorted).
*/ */
private Comparator<T> getComparatorChain(TableSortState newSortState) { private Comparator<T> getComparatorChain(TableSortState newSortState) {
if (newSortState.isUnsorted()) {
return NO_SORT_COMPARATOR;
}
ComparatorLink comparatorLink = new ComparatorLink(); ComparatorLink comparatorLink = new ComparatorLink();
for (ColumnSortState columnSortState : newSortState) { for (ColumnSortState columnSortState : newSortState) {
Comparator<T> nextComparator = getComparator(columnSortState); Comparator<T> nextComparator = getComparator(columnSortState);

View file

@ -1,6 +1,5 @@
/* ### /* ###
* IP: GHIDRA * IP: GHIDRA
* REVIEWED: YES
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -29,7 +28,7 @@ public class ColumnSortState {
private static final String XML_SORT_ORDER = "SORT_ORDER"; private static final String XML_SORT_ORDER = "SORT_ORDER";
public enum SortDirection { public enum SortDirection {
ASCENDING, DESCENDING; ASCENDING, DESCENDING, UNSORTED;
public boolean isAscending() { public boolean isAscending() {
return this == ASCENDING; return this == ASCENDING;
@ -82,9 +81,8 @@ public class ColumnSortState {
} }
public ColumnSortState createFlipState() { public ColumnSortState createFlipState() {
ColumnSortState newSortState = ColumnSortState newSortState = new ColumnSortState(columnModelIndex,
new ColumnSortState(columnModelIndex, isAscending() ? SortDirection.DESCENDING isAscending() ? SortDirection.DESCENDING : SortDirection.ASCENDING, sortOrder_OneBased);
: SortDirection.ASCENDING, sortOrder_OneBased);
return newSortState; return newSortState;
} }

View file

@ -15,8 +15,7 @@
*/ */
package docking.widgets.table; package docking.widgets.table;
import java.util.ArrayList; import java.util.*;
import java.util.List;
/** /**
* Combines multiple Table Filters into a single TableFilter that can be applied. All contained * Combines multiple Table Filters into a single TableFilter that can be applied. All contained
@ -101,4 +100,30 @@ public class CombinedTableFilter<T> implements TableFilter<T> {
} }
return false; return false;
} }
@Override
public int hashCode() {
// not meant to put in hashing structures; the data for equals may change over time
throw new UnsupportedOperationException();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
CombinedTableFilter<?> other = (CombinedTableFilter<?>) obj;
if (!Objects.equals(filters, other.filters)) {
return false;
}
return true;
}
} }

View file

@ -15,8 +15,7 @@
*/ */
package docking.widgets.table; package docking.widgets.table;
import java.util.ArrayList; import java.util.*;
import java.util.List;
import javax.swing.JLabel; import javax.swing.JLabel;
import javax.swing.table.TableColumnModel; import javax.swing.table.TableColumnModel;
@ -27,7 +26,7 @@ import ghidra.util.table.column.GColumnRenderer.ColumnConstraintFilterMode;
public class DefaultRowFilterTransformer<ROW_OBJECT> implements RowFilterTransformer<ROW_OBJECT> { public class DefaultRowFilterTransformer<ROW_OBJECT> implements RowFilterTransformer<ROW_OBJECT> {
private List<String> columnData = new ArrayList<String>(); private List<String> columnData = new ArrayList<>();
private TableColumnModel columnModel; private TableColumnModel columnModel;
private final RowObjectTableModel<ROW_OBJECT> model; private final RowObjectTableModel<ROW_OBJECT> model;
@ -129,4 +128,38 @@ public class DefaultRowFilterTransformer<ROW_OBJECT> implements RowFilterTransfo
(GColumnRenderer<Object>) column.getColumnRenderer(); (GColumnRenderer<Object>) column.getColumnRenderer();
return columnRenderer; return columnRenderer;
} }
@Override
public int hashCode() {
// not meant to put in hashing structures; the data for equals may change over time
throw new UnsupportedOperationException();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
DefaultRowFilterTransformer<?> other = (DefaultRowFilterTransformer<?>) obj;
if (!Objects.equals(columnData, other.columnData)) {
return false;
}
if (!Objects.equals(columnModel, other.columnModel)) {
return false;
}
// use '==' so that we don't end up comparing all table data
if (model != other.model) {
return false;
}
return true;
}
} }

View file

@ -1,432 +0,0 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* 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.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package docking.widgets.table;
import ghidra.util.Msg;
import ghidra.util.SystemUtilities;
import ghidra.util.datastruct.WeakDataStructureFactory;
import ghidra.util.datastruct.WeakSet;
import ghidra.util.exception.AssertException;
import java.util.*;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableModel;
import docking.widgets.table.ColumnSortState.SortDirection;
/**
* A sorter for TableModels. The sorter has a model (conforming to TableModel)
* and itself implements TableModel. TableSorter does not store or copy
* the data in the TableModel, instead it maintains an array of
* integers which it keeps the same size as the number of rows in its
* model. When the model changes it notifies the sorter that something
* has changed (e.g., "rowsAdded") so that its internal array of integers
* can be reallocated. As requests are made of the sorter (like
* getValueAt(row, col) it redirects them to its model via the mapping
* array. That way the TableSorter appears to hold another copy of the table
* with the rows in a different order. The sorting algorithm used is stable
* which means that it does not move around rows when its comparison
* function returns 0 to denote that they are equivalent.
*
* @deprecated You should instead be using {@link AbstractSortedTableModel}
*/
@Deprecated
public class DefaultSortedTableModel extends AbstractTableModel implements SortedTableModel,
TableModelListener {
// all callbacks to fire changes and add listeners are expected to be in the Swing thread
private WeakSet<SortListener> listeners =
WeakDataStructureFactory.createSingleThreadAccessWeakSet();
//========================================================================
// conversion to multi sorting tables
private TableSortState createSortState(int column, boolean ascending) {
ColumnSortState sortState =
new ColumnSortState(column, ascending ? SortDirection.ASCENDING
: SortDirection.DESCENDING, 1);
return new TableSortState(sortState);
}
public void setSort(int column, boolean ascending) {
TableSortState sortState = createSortState(column, ascending);
setTableSortState(sortState);
}
@Override
public int getPrimarySortColumnIndex() {
return tableSortState.iterator().next().getColumnModelIndex();
}
public boolean isAscending() {
return true;
}
// end conversion to multi sorting tables
private TableSortState tableSortState = new TableSortState();
private int[] indexes;
/**
* A poorly named variable that is intended to indicate that the client is making bulk updates
* and this table should not perform indexing while this bulk operation is happening.
*/
private boolean sortEnabled = true;
private Map<Integer, Comparator<?>> registeredComparatorMap =
new HashMap<Integer, Comparator<?>>();
protected TableModel model;
/**
* Construct a new TableSorter using the given model.
*
* @deprecated You should instead be using {@link AbstractSortedTableModel}
*/
@Deprecated
public DefaultSortedTableModel(TableModel model) {
if (model == null) {
throw new NullPointerException("Model cannot be null!");
}
if (model instanceof AbstractSortedTableModel<?>) {
throw new AssertException("You cannot pass an AbstractSortedTableModel to " +
getClass().getSimpleName() + "--it is already sorted!");
}
setModel(model);
init();
}
private void init() {
TableSortState defaultSortState = createSortState(0, true);
this.tableSortState = defaultSortState;
}
public void setModel(TableModel model) {
if (model == null) {
throw new NullPointerException("Model cannot be null!");
}
this.model = model;
model.addTableModelListener(this);
reallocateIndexes();
}
public TableModel getModel() {
return model;
}
/**
* Enable the sorter according to the enable parameter. This method
* should be called with enable set to <b>false</b> <i>before</i> the table
* model is populated or else a sort will be done after each row is
* inserted, and that would not be good.
* @param enable true means to enable the sorting.
*/
public void enableSorter(boolean enable) {
sortEnabled = enable;
if (!sortEnabled) {
return;
}
reallocateIndexes();
sort();
SystemUtilities.runIfSwingOrPostSwingLater(new Runnable() {
@Override
public void run() {
notifySorted();
}
});
}
private void notifySorted() {
fireTableChangedEvent();
for (SortListener listener : listeners) {
listener.modelSorted(tableSortState);
}
}
private void fireTableChangedEvent() {
fireTableChanged(new TableModelEvent(this));
}
@Override
public void addSortListener(SortListener l) {
listeners.add(l);
}
public void registerComparator(Comparator<?> comparator, int column) {
registeredComparatorMap.put(column, comparator);
}
public void deRegisterComparator(int column) {
registeredComparatorMap.remove(column);
}
@SuppressWarnings("unchecked")
// for the Comparator<?> usage
private int compareRowsByColumn(int row1, int row2, int column) {
TableModel data = model;
Object o1 = data.getValueAt(row1, column);
Object o2 = data.getValueAt(row2, column);
// If both values are null return 0
if (o1 == null && o2 == null) {
return 0;
}
else if (o1 == null) { // Define null less than everything.
return -1;
}
else if (o2 == null) {
return 1;
}
// if the user has registered a comparator, prefer that
Comparator comparator = getComparator(column);
Object value1 = data.getValueAt(row1, column);
Object value2 = data.getValueAt(row2, column);
return comparator.compare(value1, value2);
}
private Comparator<?> getComparator(int column) {
Comparator<?> comparator = registeredComparatorMap.get(column);
if (comparator != null) {
return comparator;
}
return DEFAULT_COMPARATOR;
}
private int compare(int row1, int row2) {
for (ColumnSortState columnSortState : tableSortState) {
int result = compareRowsByColumn(row1, row2, columnSortState.getColumnModelIndex());
if (result != 0) {
return columnSortState.isAscending() ? result : -result;
}
}
return 0;
}
private void reallocateIndexes() {
int rowCount = model.getRowCount();
// Set up a new array of indexes with the right number of elements
// for the new data model.
indexes = new int[rowCount];
// Initialize with the identity mapping.
for (int row = 0; row < rowCount; row++) {
indexes[row] = row;
}
}
@Override
public void tableChanged(TableModelEvent e) {
if (sortEnabled) {
reallocateIndexes();
sort();
}
fireTableChanged(e);
}
private void checkModel() {
if (indexes.length == model.getRowCount()) {
return; // O.K.!
}
if (!sortEnabled) {
return; // don't report an issue if we are called while in the middle of updating
}
Msg.error(this, "Sorter not informed of a change in model.");
}
private void sort() {
checkModel();
shuttlesort(indexes.clone(), indexes, 0, indexes.length);
}
// This is a home-grown implementation which we have not had time
// to research - it may perform poorly in some circumstances. It
// requires twice the space of an in-place algorithm and makes
// NlogN assignments shuttling the values between the two
// arrays. The number of compares appears to vary between N-1 and
// NlogN depending on the initial order but the main reason for
// using it here is that, unlike qsort, it is stable.
private void shuttlesort(int[] from, int[] to, int low, int high) {
if (high - low < 2) {
return;
}
int middle = (low + high) / 2;
shuttlesort(to, from, low, middle);
shuttlesort(to, from, middle, high);
int p = low;
int q = middle;
/* This is an optional short-cut; at each recursive call,
check to see if the elements in this subset are already
ordered. If so, no further comparisons are needed; the
sub-array can just be copied. The array must be copied rather
than assigned otherwise sister calls in the recursion might
get out of sinc. When the number of elements is three they
are partitioned so that the first set, [low, mid), has one
element and and the second, [mid, high), has two. We skip the
optimization when the number of elements is three or less as
the first compare in the normal merge will produce the same
sequence of steps. This optimization seems to be worthwhile
for partially ordered lists but some analysis is needed to
find out how the performance drops to Nlog(N) as the initial
order diminishes - it may drop very quickly. */
if (high - low >= 4 && compare(from[middle - 1], from[middle]) <= 0) {
for (int i = low; i < high; i++) {
to[i] = from[i];
}
return;
}
// A normal merge.
for (int i = low; i < high; i++) {
if (q >= high || (p < middle && compare(from[p], from[q]) <= 0)) {
to[i] = from[p++];
}
else {
to[i] = from[q++];
}
}
}
/**
* @see javax.swing.table.TableModel#getValueAt(int, int)
*/
@Override
public Object getValueAt(int aRow, int aColumn) {
checkModel();
return model.getValueAt(indexes[aRow], aColumn);
}
/**
* Converts a sorted index into an unsorted index.
* This is good if you need to access the underlying table directly by
* the unsorted index.
*/
public int getSortedIndex(int aRow) {
checkModel();
return indexes[aRow];
}
/**
* @see javax.swing.table.TableModel#setValueAt(java.lang.Object, int, int)
*/
@Override
public void setValueAt(Object aValue, int aRow, int aColumn) {
checkModel();
model.setValueAt(aValue, indexes[aRow], aColumn);
}
/**
* Sorts the model in ascending order by the specified columnIndex.
* @param column the index of the column to sort
*/
public void sortByColumn(int column) {
ColumnSortState sortState = new ColumnSortState(column, SortDirection.ASCENDING, 1);
TableSortState newCollection = new TableSortState(sortState);
setTableSortState(newCollection);
}
@Override
public boolean isSortable(int columnIndex) {
return true;
}
public void resort() {
tableChanged(new TableModelEvent(this));
}
@Override
public TableSortState getTableSortState() {
return tableSortState;
}
@Override
public void setTableSortState(TableSortState sortStates) {
this.tableSortState = sortStates;
resort();
}
//==================================================================================================
// Delegate Methods - We are a wrapper
//==================================================================================================
/**
*
* @see javax.swing.table.TableModel#getRowCount()
*/
@Override
public int getRowCount() {
// Always rely on the indexed values as the count. This prevents getValueAt() from
// being called when the indexes are out-of-date, while the client is manipulating the table
return indexes.length;
}
/**
*
* @see javax.swing.table.TableModel#getColumnCount()
*/
@Override
public int getColumnCount() {
return model.getColumnCount();
}
/**
*
* @see javax.swing.table.TableModel#getColumnName(int)
*/
@Override
public String getColumnName(int aColumn) {
return model.getColumnName(aColumn);
}
/**
*
* @see javax.swing.table.TableModel#getColumnClass(int)
*/
@Override
public Class<?> getColumnClass(int aColumn) {
return model.getColumnClass(aColumn);
}
/**
*
* @see javax.swing.table.TableModel#isCellEditable(int, int)
*/
@Override
public boolean isCellEditable(int row, int column) {
return model.isCellEditable(row, column);
}
}

View file

@ -20,8 +20,8 @@ import java.util.List;
import docking.widgets.filter.*; import docking.widgets.filter.*;
public class DefaultTableTextFilterFactory<ROW_OBJECT> implements public class DefaultTableTextFilterFactory<ROW_OBJECT>
TableTextFilterFactory<ROW_OBJECT> { implements TableTextFilterFactory<ROW_OBJECT> {
private final TextFilterFactory textFilterFactory; private final TextFilterFactory textFilterFactory;
private final boolean inverted; private final boolean inverted;
@ -40,7 +40,7 @@ public class DefaultTableTextFilterFactory<ROW_OBJECT> implements
TableFilter<ROW_OBJECT> tableFilter = getBaseFilter(text, transformer); TableFilter<ROW_OBJECT> tableFilter = getBaseFilter(text, transformer);
if (inverted && tableFilter != null) { if (inverted && tableFilter != null) {
tableFilter = new InvertedTableFilter<ROW_OBJECT>(tableFilter); tableFilter = new InvertedTableFilter<>(tableFilter);
} }
return tableFilter; return tableFilter;
} }
@ -55,14 +55,14 @@ public class DefaultTableTextFilterFactory<ROW_OBJECT> implements
if (textFilter == null) { if (textFilter == null) {
return null; return null;
} }
return new TableTextFilter<ROW_OBJECT>(textFilter, transformer); return new TableTextFilter<>(textFilter, transformer);
} }
private TableFilter<ROW_OBJECT> getMultiWordTableFilter(String text, private TableFilter<ROW_OBJECT> getMultiWordTableFilter(String text,
RowFilterTransformer<ROW_OBJECT> transformer) { RowFilterTransformer<ROW_OBJECT> transformer) {
List<TextFilter> filters = new ArrayList<TextFilter>(); List<TextFilter> filters = new ArrayList<>();
TermSplitter splitter = filterOptions.getTermSplitter(); TermSplitter splitter = filterOptions.getTermSplitter();
for (String term : splitter.split(text)) { for (String term : splitter.split(text)) {
TextFilter textFilter = textFilterFactory.getTextFilter(term); TextFilter textFilter = textFilterFactory.getTextFilter(term);
@ -70,7 +70,7 @@ public class DefaultTableTextFilterFactory<ROW_OBJECT> implements
filters.add(textFilter); filters.add(textFilter);
} }
} }
return new MultiTextFilterTableFilter<ROW_OBJECT>(text, filters, transformer, return new MultiTextFilterTableFilter<>(filters, transformer,
filterOptions.getMultitermEvaluationMode()); filterOptions.getMultitermEvaluationMode());
} }
} }

View file

@ -15,6 +15,8 @@
*/ */
package docking.widgets.table; package docking.widgets.table;
import java.util.Objects;
public class InvertedTableFilter<ROW_OBJECT> implements TableFilter<ROW_OBJECT> { public class InvertedTableFilter<ROW_OBJECT> implements TableFilter<ROW_OBJECT> {
private final TableFilter<ROW_OBJECT> filter; private final TableFilter<ROW_OBJECT> filter;
@ -34,4 +36,29 @@ public class InvertedTableFilter<ROW_OBJECT> implements TableFilter<ROW_OBJECT>
return !filter.acceptsRow(rowObject); return !filter.acceptsRow(rowObject);
} }
@Override
public int hashCode() {
// not meant to put in hashing structures; the data for equals may change over time
throw new UnsupportedOperationException();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
InvertedTableFilter<?> other = (InvertedTableFilter<?>) obj;
if (!Objects.equals(filter, other.filter)) {
return false;
}
return true;
}
} }

View file

@ -16,6 +16,7 @@
package docking.widgets.table; package docking.widgets.table;
import java.util.List; import java.util.List;
import java.util.Objects;
import docking.widgets.filter.MultitermEvaluationMode; import docking.widgets.filter.MultitermEvaluationMode;
import docking.widgets.filter.TextFilter; import docking.widgets.filter.TextFilter;
@ -23,13 +24,11 @@ import docking.widgets.filter.TextFilter;
public class MultiTextFilterTableFilter<ROW_OBJECT> implements TableFilter<ROW_OBJECT> { public class MultiTextFilterTableFilter<ROW_OBJECT> implements TableFilter<ROW_OBJECT> {
private final List<TextFilter> filters; private final List<TextFilter> filters;
private final String text;
private final RowFilterTransformer<ROW_OBJECT> transformer; private final RowFilterTransformer<ROW_OBJECT> transformer;
private final MultitermEvaluationMode evalMode; private final MultitermEvaluationMode evalMode;
public MultiTextFilterTableFilter(String text, List<TextFilter> filters, public MultiTextFilterTableFilter(List<TextFilter> filters,
RowFilterTransformer<ROW_OBJECT> transformer, MultitermEvaluationMode evalMode) { RowFilterTransformer<ROW_OBJECT> transformer, MultitermEvaluationMode evalMode) {
this.text = text;
this.filters = filters; this.filters = filters;
this.transformer = transformer; this.transformer = transformer;
this.evalMode = evalMode; this.evalMode = evalMode;
@ -90,4 +89,37 @@ public class MultiTextFilterTableFilter<ROW_OBJECT> implements TableFilter<ROW_O
// @formatter:on // @formatter:on
} }
@Override
public int hashCode() {
// not meant to put in hashing structures; the data for equals may change over time
throw new UnsupportedOperationException();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
MultiTextFilterTableFilter<?> other = (MultiTextFilterTableFilter<?>) obj;
if (evalMode != other.evalMode) {
return false;
}
if (!Objects.equals(filters, other.filters)) {
return false;
}
if (!Objects.equals(transformer, other.transformer)) {
return false;
}
return true;
}
} }

View file

@ -56,7 +56,29 @@ public interface RowObjectFilterModel<ROW_OBJECT> extends RowObjectTableModel<RO
public int getViewRow(int modelRow); public int getViewRow(int modelRow);
/**
* Returns the view index of the given item. When filtered, this is the index is the smaller,
* visible set of data; when unfiltered, this index is the same as that returned by
* {@link #getModelIndex(Object)}.
*
* <p>This operation will be O(n) unless the implementation is sorted, in which case the
* operation is O(log n), as it uses a binary search.
*
* @param t the item
* @return the view index
*/
public int getViewIndex(ROW_OBJECT t); public int getViewIndex(ROW_OBJECT t);
/**
* Returns the model index of the given item. When filtered, this is the index is the larger,
* set of data; when unfiltered, this index is the same as that returned by
* {@link #getModelIndex(Object)}.
*
* <p>This operation will be O(n) unless the implementation is sorted, in which case the
* operation is O(log n), as it uses a binary search.
*
* @param t the item
* @return the model index
*/
public int getModelIndex(ROW_OBJECT t); public int getModelIndex(ROW_OBJECT t);
} }

View file

@ -22,7 +22,6 @@ import java.util.*;
import javax.swing.*; import javax.swing.*;
import javax.swing.event.*; import javax.swing.event.*;
import javax.swing.table.TableModel;
import org.apache.commons.collections4.map.LazyMap; import org.apache.commons.collections4.map.LazyMap;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
@ -40,17 +39,8 @@ import utilities.util.reflection.ReflectionUtilities;
* filtered, the contents change (and then change back when the filter is removed). It is nice * filtered, the contents change (and then change back when the filter is removed). It is nice
* to be able to filter a table, select an item of interest, and then unfilter the table to see * to be able to filter a table, select an item of interest, and then unfilter the table to see
* that item in more context. * that item in more context.
* <p> *
* Notes on usage: * @param <T> the row type
* <ul>
* <li>Some table models are sensitive to the order in which {@link TableModel#tableChanged()}
* is called. These models should either not use this SelectionManger, or need to
* change their code to be more robust. As an example, the {@link DefaultSortedTableModel}
* updates its indexes in odd ways. Further, there is a chance that the state of its
* indexing is incorrect when <tt>tableChanged</tt> is called. So, that model has to
* account for the fact that it may get called by this class when it is in a bad state.
* </li>
* </ul>
*/ */
public class RowObjectSelectionManager<T> extends DefaultListSelectionModel public class RowObjectSelectionManager<T> extends DefaultListSelectionModel
implements SelectionManager { implements SelectionManager {
@ -109,14 +99,6 @@ public class RowObjectSelectionManager<T> extends DefaultListSelectionModel
installListSelectionListener(); installListSelectionListener();
} }
/**
* Sets the logger used by this class. Useful for debugging individual table issues.
*/
@Override
public void setLogger(Logger logger) {
this.log = logger;
}
@Override @Override
public void addSelectionManagerListener(SelectionManagerListener listener) { public void addSelectionManagerListener(SelectionManagerListener listener) {
listeners.add(listener); listeners.add(listener);

View file

@ -15,6 +15,7 @@
*/ */
package docking.widgets.table; package docking.widgets.table;
import java.util.Arrays;
import java.util.List; import java.util.List;
import javax.swing.table.TableModel; import javax.swing.table.TableModel;
@ -29,11 +30,7 @@ public interface RowObjectTableModel<T> extends TableModel {
public static TableModel unwrap(TableModel m) { public static TableModel unwrap(TableModel m) {
// TODO can we now get rid of the default sorted model usage?
TableModel model = m; TableModel model = m;
while (model instanceof DefaultSortedTableModel) {
model = ((DefaultSortedTableModel) model).getModel();
}
while (model instanceof TableModelWrapper) { while (model instanceof TableModelWrapper) {
model = ((TableModelWrapper<?>) model).getWrappedModel(); model = ((TableModelWrapper<?>) model).getWrappedModel();
} }
@ -52,6 +49,7 @@ public interface RowObjectTableModel<T> extends TableModel {
* non-filtering models the view and model rows will always be the same. * non-filtering models the view and model rows will always be the same.
* *
* @param viewRow the row for which to return a row object. * @param viewRow the row for which to return a row object.
* @return the row object
*/ */
public T getRowObject(int viewRow); public T getRowObject(int viewRow);
@ -77,7 +75,7 @@ public interface RowObjectTableModel<T> extends TableModel {
* the underlying data and not a copy, as this method will potentially sort the given data. * the underlying data and not a copy, as this method will potentially sort the given data.
* <p> * <p>
* For those subclasses using an array, you may use the <tt>Arrays</tt> class to create * For those subclasses using an array, you may use the <tt>Arrays</tt> class to create
* a list backed by the array ({@link Arrays#asList(Object...)). * a list backed by the array ({@link Arrays#asList(Object...)}).
* @return the model data. * @return the model data.
*/ */
public List<T> getModelData(); public List<T> getModelData();

View file

@ -17,9 +17,6 @@ package docking.widgets.table;
import javax.swing.ListSelectionModel; import javax.swing.ListSelectionModel;
import javax.swing.event.TableModelListener; import javax.swing.event.TableModelListener;
import javax.swing.table.TableModel;
import org.apache.logging.log4j.Logger;
/** /**
* A class to track and restore selections made in a table. We use this in the docking * A class to track and restore selections made in a table. We use this in the docking
@ -27,17 +24,6 @@ import org.apache.logging.log4j.Logger;
* filtered, the contents change (and then change back when the filter is removed). It is nice * filtered, the contents change (and then change back when the filter is removed). It is nice
* to be able to filter a table, select an item of interest, and then unfilter the table to see * to be able to filter a table, select an item of interest, and then unfilter the table to see
* that item in more context. * that item in more context.
* <p>
* Notes on usage:
* <ul>
* <li>Some table models are sensitive to the order in which {@link TableModel#tableChanged()}
* is called. These models should either not use this SelectionManger, or need to
* change their code to be more robust. As an example, the {@link DefaultSortedTableModel}
* updates its indexes in odd ways. Further, there is a chance that the state of its
* indexing is incorrect when <tt>tableChanged</tt> is called. So, that model has to
* account for the fact that it may get called by this class when it is in a bad state.
* </li>
* </ul>
*/ */
public interface SelectionManager extends ListSelectionModel, TableModelListener { public interface SelectionManager extends ListSelectionModel, TableModelListener {
@ -47,12 +33,5 @@ public interface SelectionManager extends ListSelectionModel, TableModelListener
public void clearSavedSelection(); public void clearSavedSelection();
/**
* Allows clients to enable tracing by providing a logger with tracing enabled.
* @param logger The logger to be used by this manager, which has tracing embedded in its
* code.
*/
public void setLogger(Logger logger);
public void dispose(); public void dispose();
} }

View file

@ -1,6 +1,5 @@
/* ### /* ###
* IP: GHIDRA * IP: GHIDRA
* REVIEWED: YES
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -32,7 +31,15 @@ public class TableSortState implements Iterable<ColumnSortState> {
private static final String XML_TABLE_SORT_STATE = "TABLE_SORT_STATE"; private static final String XML_TABLE_SORT_STATE = "TABLE_SORT_STATE";
private List<ColumnSortState> columnSortStates = new ArrayList<ColumnSortState>(); private List<ColumnSortState> columnSortStates = new ArrayList<>();
/**
* Creates a sort state that represents being unsorted
* @return a sort state that represents being unsorted
*/
public static TableSortState createUnsortedSortState() {
return new TableSortState();
}
/** /**
* Creates a sort state with the given column as the sorted column (sorted ascending). * Creates a sort state with the given column as the sorted column (sorted ascending).
@ -64,8 +71,8 @@ public class TableSortState implements Iterable<ColumnSortState> {
} }
public TableSortState(List<ColumnSortState> sortStates) { public TableSortState(List<ColumnSortState> sortStates) {
List<Integer> sortOrders = new ArrayList<Integer>(); List<Integer> sortOrders = new ArrayList<>();
List<Integer> columnIndices = new ArrayList<Integer>(); List<Integer> columnIndices = new ArrayList<>();
for (ColumnSortState state : sortStates) { for (ColumnSortState state : sortStates) {
int sortOrder = state.getSortOrder(); int sortOrder = state.getSortOrder();
if (sortOrders.contains(sortOrder)) { if (sortOrders.contains(sortOrder)) {
@ -84,7 +91,7 @@ public class TableSortState implements Iterable<ColumnSortState> {
columnIndices.add(columnModelIndex); columnIndices.add(columnModelIndex);
} }
this.columnSortStates = new ArrayList<ColumnSortState>(sortStates); this.columnSortStates = new ArrayList<>(sortStates);
} }
public TableSortState(ColumnSortState columnSortState) { public TableSortState(ColumnSortState columnSortState) {
@ -100,6 +107,10 @@ public class TableSortState implements Iterable<ColumnSortState> {
return columnSortStates.size(); return columnSortStates.size();
} }
public boolean isUnsorted() {
return columnSortStates.isEmpty();
}
public ColumnSortState getColumnSortState(int columnIndex) { public ColumnSortState getColumnSortState(int columnIndex) {
for (ColumnSortState sortState : columnSortStates) { for (ColumnSortState sortState : columnSortStates) {
if (sortState.getColumnModelIndex() == columnIndex) { if (sortState.getColumnModelIndex() == columnIndex) {
@ -110,7 +121,7 @@ public class TableSortState implements Iterable<ColumnSortState> {
} }
public List<ColumnSortState> getAllSortStates() { public List<ColumnSortState> getAllSortStates() {
return new ArrayList<ColumnSortState>(columnSortStates); return new ArrayList<>(columnSortStates);
} }
@Override @Override
@ -140,7 +151,7 @@ public class TableSortState implements Iterable<ColumnSortState> {
Element sortStateElement = (Element) tableSortStateElementList.get(0); Element sortStateElement = (Element) tableSortStateElementList.get(0);
List<?> children = sortStateElement.getChildren(ColumnSortState.XML_COLUMN_SORT_STATE); List<?> children = sortStateElement.getChildren(ColumnSortState.XML_COLUMN_SORT_STATE);
List<ColumnSortState> columnStates = new ArrayList<ColumnSortState>(children.size()); List<ColumnSortState> columnStates = new ArrayList<>(children.size());
for (Object object : children) { for (Object object : children) {
columnStates.add(ColumnSortState.restoreFromXML((Element) object)); columnStates.add(ColumnSortState.restoreFromXML((Element) object));
} }

View file

@ -1,6 +1,5 @@
/* ### /* ###
* IP: GHIDRA * IP: GHIDRA
* REVIEWED: YES
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,8 +15,7 @@
*/ */
package docking.widgets.table; package docking.widgets.table;
import java.util.Comparator; import java.util.*;
import java.util.Iterator;
public class TableSortingContext<T> { public class TableSortingContext<T> {
@ -25,8 +23,8 @@ public class TableSortingContext<T> {
private Comparator<T> comparator; private Comparator<T> comparator;
public TableSortingContext(TableSortState sortState, Comparator<T> comparator) { public TableSortingContext(TableSortState sortState, Comparator<T> comparator) {
this.sortState = sortState; this.sortState = Objects.requireNonNull(sortState);
this.comparator = comparator; this.comparator = Objects.requireNonNull(comparator);
} }
public Comparator<T> getComparator() { public Comparator<T> getComparator() {
@ -37,6 +35,15 @@ public class TableSortingContext<T> {
return sortState; return sortState;
} }
/**
* Returns true if there are no columns marked as sorted, which represents a 'no sort' state
*
* @return true if there are no columns sorted
*/
public boolean isUnsorted() {
return sortState.isUnsorted();
}
public boolean isReverseOf(TableSortingContext<T> otherContext) { public boolean isReverseOf(TableSortingContext<T> otherContext) {
int sortedColumnCount = sortState.getSortedColumnCount(); int sortedColumnCount = sortState.getSortedColumnCount();
if (sortedColumnCount != 1) { if (sortedColumnCount != 1) {

View file

@ -16,6 +16,7 @@
package docking.widgets.table; package docking.widgets.table;
import java.util.List; import java.util.List;
import java.util.Objects;
import docking.widgets.filter.TextFilter; import docking.widgets.filter.TextFilter;
@ -56,6 +57,35 @@ public class TableTextFilter<ROW_OBJECT> implements TableFilter<ROW_OBJECT> {
return false; return false;
} }
@Override
public int hashCode() {
// not meant to put in hashing structures; the data for equals may change over time
throw new UnsupportedOperationException();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
TableTextFilter<?> other = (TableTextFilter<?>) obj;
if (!Objects.equals(textFilter, other.textFilter)) {
return false;
}
if (!Objects.equals(transformer, other.transformer)) {
return false;
}
return true;
}
@Override @Override
public String toString() { public String toString() {
return getClass().getSimpleName() + " - filter='" + textFilter.getFilterText() + "'"; return getClass().getSimpleName() + " - filter='" + textFilter.getFilterText() + "'";

View file

@ -15,8 +15,6 @@
*/ */
package docking.widgets.table; package docking.widgets.table;
import java.awt.Toolkit;
import javax.swing.JTable; import javax.swing.JTable;
import javax.swing.table.*; import javax.swing.table.*;
@ -113,11 +111,17 @@ public class TableUtils {
TableSortStateEditor editor = new TableSortStateEditor(columnSortStates); TableSortStateEditor editor = new TableSortStateEditor(columnSortStates);
if (editor.isColumnSorted(modelColumnIndex)) { if (editor.isColumnSorted(modelColumnIndex)) {
// remove it. If there is only one, don't remove the last one
if (editor.getSortedColumnCount() == 1) { /*
Toolkit.getDefaultToolkit().beep(); Note: this code allows us to disable the 'unsorting' of a table via the UI
return;
} // remove it. If there is only one, don't remove the last one
if (editor.getSortedColumnCount() == 1) {
Toolkit.getDefaultToolkit().beep();
return;
}
*/
editor.removeSortedColumn(modelColumnIndex); editor.removeSortedColumn(modelColumnIndex);
} }
else { else {

View file

@ -318,10 +318,6 @@ public class ColumnBasedTableFilter<R> implements TableFilter<R> {
list.add(constraintSet); list.add(constraintSet);
} }
public boolean isEmpty() {
return list.isEmpty();
}
boolean acceptsRow(R rowObject) { boolean acceptsRow(R rowObject) {
for (ColumnConstraintSet<R, ?> constraintSet : list) { for (ColumnConstraintSet<R, ?> constraintSet : list) {
if (!constraintSet.accepts(rowObject, tableFilterContext)) { if (!constraintSet.accepts(rowObject, tableFilterContext)) {

View file

@ -1,6 +1,5 @@
/* ### /* ###
* IP: GHIDRA * IP: GHIDRA
* REVIEWED: YES
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,17 +15,30 @@
*/ */
package docking.widgets.table.threaded; package docking.widgets.table.threaded;
import ghidra.util.task.TaskMonitor;
import java.util.List; import java.util.List;
import docking.widgets.table.AddRemoveListItem; import docking.widgets.table.AddRemoveListItem;
import ghidra.util.task.TaskMonitor;
public class AddRemoveJob<T> extends TableUpdateJob<T> { public class AddRemoveJob<T> extends TableUpdateJob<T> {
AddRemoveJob(ThreadedTableModel<T, ?> model, List<AddRemoveListItem<T>> addRemoveList, AddRemoveJob(ThreadedTableModel<T, ?> model, List<AddRemoveListItem<T>> addRemoveList,
TaskMonitor monitor) { TaskMonitor monitor) {
super(model, monitor); super(model, monitor);
setForceFilter(false); // the item will do its own sorting and filtering
this.addRemoveList = addRemoveList; this.addRemoveList = addRemoveList;
} }
@Override
public synchronized boolean requestFilter() {
//
// This is a request to fully filter the table's data (like when the filter changes).
// In this case, we had disabled 'force filter', as the sorting did not need it.
// However, when the client asks to filter, make sure we filter.
//
boolean canFilter = super.requestFilter();
if (canFilter) {
setForceFilter(true); // reset, since we had turned it off above; now we have to filter
}
return canFilter;
}
} }

View file

@ -1,6 +1,5 @@
/* ### /* ###
* IP: GHIDRA * IP: GHIDRA
* REVIEWED: YES
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -22,6 +21,6 @@ public class LoadJob<T> extends TableUpdateJob<T> {
LoadJob(ThreadedTableModel<T, ?> model, TaskMonitor monitor) { LoadJob(ThreadedTableModel<T, ?> model, TaskMonitor monitor) {
super(model, monitor); super(model, monitor);
reload(); // set job to totally reload data; reload(); // set job to totally reload data;
sort(model.getSortingContext(), false); // set the comparator so the data will be sorted requestSort(model.getSortingContext(), false); // set the comparator so the data will be sorted
} }
} }

View file

@ -1,6 +1,5 @@
/* ### /* ###
* IP: GHIDRA * IP: GHIDRA
* REVIEWED: YES
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -26,6 +25,6 @@ public class LoadSpecificDataJob<T> extends TableUpdateJob<T> {
// set the comparator so the data will be sorted; always force a sort, since we just // set the comparator so the data will be sorted; always force a sort, since we just
// loaded the data // loaded the data
sort(model.getSortingContext(), true); requestSort(model.getSortingContext(), true);
} }
} }

View file

@ -38,4 +38,23 @@ public class NullTableFilter<ROW_OBJECT> implements TableFilter<ROW_OBJECT> {
// to filter, which doesn't make sense if this is meant to only be used by itself. // to filter, which doesn't make sense if this is meant to only be used by itself.
return false; return false;
} }
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
return true;
}
@Override
public int hashCode() {
return getClass().hashCode();
}
} }

View file

@ -1,6 +1,5 @@
/* ### /* ###
* IP: GHIDRA * IP: GHIDRA
* REVIEWED: YES
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,8 +15,8 @@
*/ */
package docking.widgets.table.threaded; package docking.widgets.table.threaded;
import ghidra.util.task.TaskMonitor;
import docking.widgets.table.TableSortingContext; import docking.widgets.table.TableSortingContext;
import ghidra.util.task.TaskMonitor;
public class SortJob<T> extends TableUpdateJob<T> { public class SortJob<T> extends TableUpdateJob<T> {
@ -25,12 +24,17 @@ public class SortJob<T> extends TableUpdateJob<T> {
TableSortingContext<T> sortingContext, boolean forceSort) { TableSortingContext<T> sortingContext, boolean forceSort) {
super(model, monitor); super(model, monitor);
setForceFilter(false); // only sorting setForceFilter(false); // only sorting
sort(sortingContext, forceSort); requestSort(sortingContext, forceSort);
} }
@Override @Override
public synchronized boolean filter() { public synchronized boolean requestFilter() {
boolean canFilter = super.filter(); //
// This is a request to fully filter the table's data (like when the filter changes).
// In this case, we had disabled 'force filter', as the sorting did not need it.
// However, when the client asks to filter, make sure we filter.
//
boolean canFilter = super.requestFilter();
if (canFilter) { if (canFilter) {
setForceFilter(true); // reset, since we had turned it off above; now we have to filter setForceFilter(true); // reset, since we had turned it off above; now we have to filter
} }

View file

@ -83,9 +83,13 @@ public class TableData<ROW_OBJECT> implements Iterable<ROW_OBJECT> {
} }
TableData<ROW_OBJECT> copy() { TableData<ROW_OBJECT> copy() {
return copy(source);
}
TableData<ROW_OBJECT> copy(TableData<ROW_OBJECT> newSource) {
List<ROW_OBJECT> dataCopy = new ArrayList<>(data); List<ROW_OBJECT> dataCopy = new ArrayList<>(data);
TableData<ROW_OBJECT> newData = new TableData<>(dataCopy, sortContext); TableData<ROW_OBJECT> newData = new TableData<>(dataCopy, sortContext);
newData.source = source; newData.source = newSource;
newData.tableFilter = tableFilter; newData.tableFilter = tableFilter;
newData.ID = ID; // it is a copy, but represents the same data newData.ID = ID; // it is a copy, but represents the same data
return newData; return newData;
@ -104,7 +108,7 @@ public class TableData<ROW_OBJECT> implements Iterable<ROW_OBJECT> {
} }
boolean isSorted() { boolean isSorted() {
return sortContext != null; return sortContext != null && !sortContext.isUnsorted();
} }
void setSortContext(TableSortingContext<ROW_OBJECT> sortContext) { void setSortContext(TableSortingContext<ROW_OBJECT> sortContext) {
@ -128,12 +132,25 @@ public class TableData<ROW_OBJECT> implements Iterable<ROW_OBJECT> {
} }
/** /**
* Uses the current sort to perform a fast lookup of the given item in the given list. * Uses the current sort to perform a fast lookup of the given item in the given list when
* sorted; a brute-force lookup when not sorted
* @param t the item
* @return the index
*/ */
int indexOf(ROW_OBJECT t) { int indexOf(ROW_OBJECT t) {
Comparator<ROW_OBJECT> comparator = sortContext.getComparator(); if (!sortContext.isUnsorted()) {
int index = Collections.binarySearch(data, t, comparator); Comparator<ROW_OBJECT> comparator = sortContext.getComparator();
return index; return Collections.binarySearch(data, t, comparator);
}
// brute force
for (int i = 0; i < data.size(); i++) {
ROW_OBJECT item = data.get(i);
if (t.equals(item)) {
return i;
}
}
return -1;
} }
boolean remove(ROW_OBJECT o) { boolean remove(ROW_OBJECT o) {
@ -145,9 +162,9 @@ public class TableData<ROW_OBJECT> implements Iterable<ROW_OBJECT> {
} }
/** /**
* Adds the new <tt>value</tt> to the data at the appropriate location based on the sort. * Adds the new <tt>value</tt> to the data at the appropriate location based on the sort
* @param value the row Object to insert. *
* @param comparator the comparator to use to find the appropriate location to insert the new data. * @param value the row Object to insert
*/ */
void insert(ROW_OBJECT value) { void insert(ROW_OBJECT value) {
@ -160,10 +177,8 @@ public class TableData<ROW_OBJECT> implements Iterable<ROW_OBJECT> {
return; // this item is filtered out of this data return; // this item is filtered out of this data
} }
if (sortContext == null) { if (!isSorted()) {
// // Not yet sorted or intentionally unsorted; just add the item, it will get sorted later
// Not yet sorted; just add the item anywhere and it will get sorted later
//
data.add(value); data.add(value);
return; return;
} }
@ -180,7 +195,7 @@ public class TableData<ROW_OBJECT> implements Iterable<ROW_OBJECT> {
// The search thinks the item is in the list because a compareTo() result of 0 was // The search thinks the item is in the list because a compareTo() result of 0 was
// found. If the two objects are not equal(), then add the new value. // found. If the two objects are not equal(), then add the new value.
ROW_OBJECT existingValue = data.get(index); ROW_OBJECT existingValue = data.get(index);
if (!SystemUtilities.isEqual(value, existingValue)) { if (!Objects.equals(value, existingValue)) {
data.add(index, value); data.add(index, value);
} }
} }
@ -248,9 +263,11 @@ public class TableData<ROW_OBJECT> implements Iterable<ROW_OBJECT> {
* @return true if the source data nor the filter are different that what is used by this object. * @return true if the source data nor the filter are different that what is used by this object.
*/ */
boolean matchesFilter(TableFilter<ROW_OBJECT> filter) { boolean matchesFilter(TableFilter<ROW_OBJECT> filter) {
// O.K., we are derived from the same source data, if the filter is the same, then there // O.K., we are derived from the same source data, if the filter is the same, then there
// is no need to refilter // is no need to refilter.
//
// Note: if a given filter does not override equals(), then this really means that they
// must be the same filter for this method to return true
return SystemUtilities.isEqual(tableFilter, filter); return SystemUtilities.isEqual(tableFilter, filter);
} }
@ -277,6 +294,16 @@ public class TableData<ROW_OBJECT> implements Iterable<ROW_OBJECT> {
return source.isUnrelatedTo(other); return source.isUnrelatedTo(other);
} }
/**
* Returns the ID of this table data. It is possible that two data instances of this class
* that have the same ID are considered to be the same data.
*
* @return the ID
*/
int getId() {
return ID;
}
/** /**
* Returns the root dataset for this data and all its ancestors. * Returns the root dataset for this data and all its ancestors.
* @return the root dataset for this data and all its ancestors. * @return the root dataset for this data and all its ancestors.
@ -310,4 +337,10 @@ public class TableData<ROW_OBJECT> implements Iterable<ROW_OBJECT> {
// of 'data', as that could be expensive. // of 'data', as that could be expensive.
return super.equals(obj); return super.equals(obj);
} }
@Override
final public int hashCode() {
// Made final to match equals()
return super.hashCode();
}
} }

View file

@ -166,7 +166,8 @@ public class TableUpdateJob<T> {
* *
* @param item the add/remove item to add to the list of items to be processed in the add/remove * @param item the add/remove item to add to the list of items to be processed in the add/remove
* phase of this job. * phase of this job.
* @param the maximum number of add/remove jobs to queue before performing a full reload * @param maxAddRemoveCount the maximum number of add/remove jobs to queue before performing
* a full reload
*/ */
public synchronized void addRemove(AddRemoveListItem<T> item, int maxAddRemoveCount) { public synchronized void addRemove(AddRemoveListItem<T> item, int maxAddRemoveCount) {
if (currentState != NOT_RUNNING) { if (currentState != NOT_RUNNING) {
@ -203,7 +204,7 @@ public class TableUpdateJob<T> {
* @return true if the sort can be processed by this job, false if this job is essentially already * @return true if the sort can be processed by this job, false if this job is essentially already
* completed and therefor cannot perform the sort job. * completed and therefor cannot perform the sort job.
*/ */
public synchronized boolean sort(TableSortingContext<T> newSortingContext, boolean forceSort) { public synchronized boolean requestSort(TableSortingContext<T> newSortingContext, boolean forceSort) {
if (currentState == DONE) { if (currentState == DONE) {
return false; return false;
} }
@ -219,27 +220,27 @@ public class TableUpdateJob<T> {
/** /**
* Tells the job that the filter criteria has changed. This method can be called on * Tells the job that the filter criteria has changed. This method can be called on
* the currently running job as well as the pending job. If called on the running job, the effect * the currently running job as well as the pending job. If called on the running job, the
* depends on the running job's state: * effect depends on the running job's state:
* <ul> * <ul>
* <li>If the filter state hasn't happened yet, then nothing needs to be done as this job * <li>If the filter state hasn't happened yet, then nothing needs to be done as this job
* will filter later anyway. * will filter later anyway.
* <li>If the filter state has already been started or completed, then this method * <li>If the filter state has already been started or completed, then this method
* attempts to stop the current process phase and cause the state machine to return to the * attempts to stop the current process phase and cause the state machine to
* filter phase. * return to the filter phase.
* <li>If the current job has already entered the DONE state, then the filter cannot take * <li>If the current job has already entered the DONE state, then the filter cannot take
* effect in this job and a false value is returned to indicate the * effect in this job and a false value is returned to indicate the filter was
* filter was not handled by this job. * not handled by this job.
* </ul> * </ul>
* @return true if the filter can be processed by this job, false if this job is essentially already * @return true if the filter can be processed by this job, false if this job is essentially already
* completed and therefor cannot perform the filter job. * completed and therefor cannot perform the filter job.
*/ */
public synchronized boolean filter() { public synchronized boolean requestFilter() {
if (currentState == DONE) { if (currentState == DONE) {
return false; return false;
} }
if (hasFiltered()) { if (hasFiltered()) {
// the user has requested a new filter, and we've already filtered, so we need to filter again // the user has requested a new filter; we've already filtered, so filter again
monitor.cancel(); monitor.cancel();
pendingRequestedState = FILTERING; pendingRequestedState = FILTERING;
} }
@ -457,6 +458,11 @@ public class TableUpdateJob<T> {
/** True if the sort applied to the table is not the same as that in the source dataset */ /** True if the sort applied to the table is not the same as that in the source dataset */
private boolean tableSortDiffersFromSourceData() { private boolean tableSortDiffersFromSourceData() {
// Note: at this point in time we do not check to see if the table is user-unsorted. It
// doesn't seem to hurt to leave the original source data sorted, even if the
// current context is 'unsorted'. In that case, this method will return true,
// that the sorts are different. But, later in this job, we check the new sort and
// do not perform sorting when 'unsorted'
return !SystemUtilities.isEqual(sourceData.getSortContext(), model.getSortingContext()); return !SystemUtilities.isEqual(sourceData.getSortContext(), model.getSortingContext());
} }
@ -498,6 +504,11 @@ public class TableUpdateJob<T> {
} }
private void doSortData(List<T> data) { private void doSortData(List<T> data) {
if (newSortContext.isUnsorted()) {
return;
}
int size = data.size(); int size = data.size();
monitor.setMessage("Sorting " + model.getName() + " (" + size + " rows)" + "..."); monitor.setMessage("Sorting " + model.getName() + " (" + size + " rows)" + "...");
monitor.initialize(size); monitor.initialize(size);
@ -512,7 +523,7 @@ public class TableUpdateJob<T> {
// //
// Usually the source data is sorted before any filter is applied. However, this is not // Usually the source data is sorted before any filter is applied. However, this is not
// the case when a load of new data is followed directly by a filter action. We rely on // the case when a load of new data is followed directly by a filter action. We rely on
// the source data being filtered in order to perform fast translations from the table's // the source data being sorted in order to perform fast translations from the table's
// view to the table's model when it is filtered. Thus, make sure that any time we are // view to the table's model when it is filtered. Thus, make sure that any time we are
// sorting the filtered data, that the source data too is sorted. // sorting the filtered data, that the source data too is sorted.
// //
@ -595,23 +606,24 @@ public class TableUpdateJob<T> {
List<T> list = filterSourceData.getData(); List<T> list = filterSourceData.getData();
List<T> result = model.doFilter(list, lastSortContext, monitor); List<T> result = model.doFilter(list, lastSortContext, monitor);
if (result != list) { // yes, '==' if (result == list) { // yes, '=='
// no filtering took place
updatedData = filterSourceData;
}
else {
// the derived data is sorted the same as the source data // the derived data is sorted the same as the source data
TableSortingContext<T> sortContext = filterSourceData.getSortContext(); TableSortingContext<T> sortContext = filterSourceData.getSortContext();
updatedData = TableData.createSubDataset(filterSourceData, result, sortContext); updatedData = TableData.createSubDataset(filterSourceData, result, sortContext);
updatedData.setTableFilter(model.getTableFilter()); updatedData.setTableFilter(model.getTableFilter());
} }
else {
// no filtering took place
updatedData = filterSourceData;
}
monitor.setMessage( monitor.setMessage(
"Done filtering " + model.getName() + " (" + updatedData.size() + " rows)"); "Done filtering " + model.getName() + " (" + updatedData.size() + " rows)");
} }
private void copyCurrentFilterData() { private void copyCurrentFilterData() {
TableData<T> currentFilteredData = getCurrentFilteredData(); TableData<T> currentFilteredData = getCurrentFilteredData();
updatedData = currentFilteredData.copy(); // copy so we don't modify the UIs version updatedData = currentFilteredData.copy(sourceData); // copy; don't modify the UI's version
// We are re-using the filtered data, so use too its sort // We are re-using the filtered data, so use too its sort
lastSortContext = updatedData.getSortContext(); lastSortContext = updatedData.getSortContext();

View file

@ -27,7 +27,6 @@ import ghidra.util.SystemUtilities;
import ghidra.util.datastruct.*; import ghidra.util.datastruct.*;
import ghidra.util.exception.*; import ghidra.util.exception.*;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
import ghidra.util.task.TaskMonitorAdapter;
import ghidra.util.worker.Worker; import ghidra.util.worker.Worker;
/** /**
@ -36,14 +35,14 @@ import ghidra.util.worker.Worker;
* You can optionally set this model to load data incrementally by passing the correct * You can optionally set this model to load data incrementally by passing the correct
* constructor argument. Note, if you make this model incremental, then you need to set an * constructor argument. Note, if you make this model incremental, then you need to set an
* incremental task monitor in order to get feedback about loading * incremental task monitor in order to get feedback about loading
* (see {@link #setIncrementalTaskMonitor(TaskMonitor)). Alternatively, you can use * (see {@link #setIncrementalTaskMonitor(TaskMonitor)}. Alternatively, you can use
* a {@link GThreadedTablePanel}, which will install the proper monitor for you. * a {@link GThreadedTablePanel}, which will install the proper monitor for you.
* *
* @param ROW_OBJECT the row object class for this table model. * @param <ROW_OBJECT> the row object class for this table model.
* @param DATA_SOURCE the type of data that will be returned from {@link #getDataSource()}. This * @param <DATA_SOURCE> the type of data that will be returned from {@link #getDataSource()}. This
* object will be given to the {@link DynamicTableColumn} objects used by this * object will be given to the {@link DynamicTableColumn} objects used by this
* table model when * table model when
* {@link DynamicTableColumn#getValue(Object, generic.settings.Settings, Object, ServiceProvider)} * {@link DynamicTableColumn#getValue(Object, ghidra.docking.settings.Settings, Object, ServiceProvider)}
* is called. * is called.
*/ */
public abstract class ThreadedTableModel<ROW_OBJECT, DATA_SOURCE> public abstract class ThreadedTableModel<ROW_OBJECT, DATA_SOURCE>
@ -52,7 +51,7 @@ public abstract class ThreadedTableModel<ROW_OBJECT, DATA_SOURCE>
private ThreadedTableModelUpdateMgr<ROW_OBJECT> updateManager; private ThreadedTableModelUpdateMgr<ROW_OBJECT> updateManager;
private boolean loadIncrementally; private boolean loadIncrementally;
private TaskMonitor incrementalMonitor = TaskMonitorAdapter.DUMMY_MONITOR; private TaskMonitor incrementalMonitor = TaskMonitor.DUMMY;
private ConcurrentListenerSet<ThreadedTableModelListener> listeners = private ConcurrentListenerSet<ThreadedTableModelListener> listeners =
new ConcurrentListenerSet<>(); new ConcurrentListenerSet<>();
@ -157,8 +156,12 @@ public abstract class ThreadedTableModel<ROW_OBJECT, DATA_SOURCE>
/** /**
* A package-level method. Subclasses should not call this. * A package-level method. Subclasses should not call this.
* <p> *
* This exists to handle whether this model should load incrementally. * <p>This exists to handle whether this model should load incrementally.
*
* @param monitor the monitor
* @return the loaded data
* @throws CancelledException
*/ */
final List<ROW_OBJECT> load(TaskMonitor monitor) throws CancelledException { final List<ROW_OBJECT> load(TaskMonitor monitor) throws CancelledException {
if (loadIncrementally) { if (loadIncrementally) {
@ -261,7 +264,7 @@ public abstract class ThreadedTableModel<ROW_OBJECT, DATA_SOURCE>
* *
* @param rowObject The object for which to search * @param rowObject The object for which to search
* @return The index for the given object; a negative value if the object is not in the list * @return The index for the given object; a negative value if the object is not in the list
* @see #getIndexForRowObject(Object); * @see #getIndexForRowObject(Object)
*/ */
protected int getUnfilteredIndexForRowObject(ROW_OBJECT rowObject) { protected int getUnfilteredIndexForRowObject(ROW_OBJECT rowObject) {
return getIndexForRowObject(rowObject, getUnfilteredData()); return getIndexForRowObject(rowObject, getUnfilteredData());
@ -327,6 +330,7 @@ public abstract class ThreadedTableModel<ROW_OBJECT, DATA_SOURCE>
/** /**
* Returns the current sorting context, which is the next one to be applied, if a sort is * Returns the current sorting context, which is the next one to be applied, if a sort is
* pending; otherwise the current sorting context. * pending; otherwise the current sorting context.
* @return the sort context
*/ */
TableSortingContext<ROW_OBJECT> getSortingContext() { TableSortingContext<ROW_OBJECT> getSortingContext() {
if (pendingSortContext != null) { if (pendingSortContext != null) {
@ -365,8 +369,7 @@ public abstract class ThreadedTableModel<ROW_OBJECT, DATA_SOURCE>
/** /**
* Override this to change how filtering is performed. This implementation will do nothing * Override this to change how filtering is performed. This implementation will do nothing
* if a <tt>TableFilter</tt> has not been set via a call to {@link #setTableFilter(TableFilter)}. * if a <tt>TableFilter</tt> has not been set via a call to {@link #setTableFilter(TableFilter)}.
* Also, no filtering will happen if there is no filter text set via a call to *
* {@link #setFilterText(String)}.
* *
* @param data The list of data to be filtered. * @param data The list of data to be filtered.
* *
@ -483,7 +486,11 @@ public abstract class ThreadedTableModel<ROW_OBJECT, DATA_SOURCE>
SystemUtilities.assertThisIsTheSwingThread("Must be called on the Swing thread"); SystemUtilities.assertThisIsTheSwingThread("Must be called on the Swing thread");
boolean dataChanged = (this.filteredData.size() != filteredData.size()); //@formatter:off
// The data is changed when it is filtered OR when an item has been added or removed
boolean dataChanged = this.filteredData.getId() != filteredData.getId() ||
this.filteredData.size() != filteredData.size();
//@formatter:on
this.allData = allData; this.allData = allData;
this.filteredData = filteredData; this.filteredData = filteredData;
@ -629,10 +636,6 @@ public abstract class ThreadedTableModel<ROW_OBJECT, DATA_SOURCE>
return filteredData.size(); return filteredData.size();
} }
public int getUnfilteredCount() {
return allData.size();
}
/** /**
* Given a row index for the raw (unfiltered) model, return the corresponding index in the * Given a row index for the raw (unfiltered) model, return the corresponding index in the
* view (filtered) model. * view (filtered) model.
@ -643,11 +646,12 @@ public abstract class ThreadedTableModel<ROW_OBJECT, DATA_SOURCE>
*/ */
@Override @Override
public int getViewRow(int modelRow) { public int getViewRow(int modelRow) {
if (getRowCount() == getUnfilteredCount()) { int unfilteredCount = getUnfilteredRowCount();
if (getRowCount() == unfilteredCount) {
return modelRow; // same list; no need to translate values return modelRow; // same list; no need to translate values
} }
if (modelRow >= allData.size()) { if (modelRow >= unfilteredCount) {
return -1; // out-of-bounds request return -1; // out-of-bounds request
} }
@ -665,7 +669,7 @@ public abstract class ThreadedTableModel<ROW_OBJECT, DATA_SOURCE>
*/ */
@Override @Override
public int getModelRow(int viewRow) { public int getModelRow(int viewRow) {
if (getRowCount() == getUnfilteredCount()) { if (getRowCount() == getUnfilteredRowCount()) {
return viewRow; // same list; no need to translate values return viewRow; // same list; no need to translate values
} }
@ -715,8 +719,10 @@ public abstract class ThreadedTableModel<ROW_OBJECT, DATA_SOURCE>
/** /**
* Sets the update delay, which is how long the model should wait before updating, after * Sets the update delay, which is how long the model should wait before updating, after
* a change has been made the data. * a change has been made the data
*
* @param updateDelayMillis the new update delay * @param updateDelayMillis the new update delay
* @param maxUpdateDelayMillis the new max update delay; updates will not wait past this time
*/ */
void setUpdateDelay(int updateDelayMillis, int maxUpdateDelayMillis) { void setUpdateDelay(int updateDelayMillis, int maxUpdateDelayMillis) {
this.minUpdateDelayMillis = updateDelayMillis; this.minUpdateDelayMillis = updateDelayMillis;
@ -724,16 +730,11 @@ public abstract class ThreadedTableModel<ROW_OBJECT, DATA_SOURCE>
updateManager.setUpdateDelay(updateDelayMillis, maxUpdateDelayMillis); updateManager.setUpdateDelay(updateDelayMillis, maxUpdateDelayMillis);
} }
/** // see setUpdateDelay
* @see #setMinDelay(int)
*/
long getMinDelay() { long getMinDelay() {
return minUpdateDelayMillis; return minUpdateDelayMillis;
} }
/**
* @see #setMaxDelay(int)
*/
long getMaxDelay() { long getMaxDelay() {
return maxUpdateDelayMillis; return maxUpdateDelayMillis;
} }

View file

@ -211,12 +211,12 @@ class ThreadedTableModelUpdateMgr<T> {
void sort(TableSortingContext<T> sortingContext, boolean forceSort) { void sort(TableSortingContext<T> sortingContext, boolean forceSort) {
synchronized (updateManager) { synchronized (updateManager) {
if (currentJob != null && pendingJob == null && if (currentJob != null && pendingJob == null &&
currentJob.sort(sortingContext, forceSort)) { currentJob.requestSort(sortingContext, forceSort)) {
return; return;
} }
if (pendingJob != null) { if (pendingJob != null) {
pendingJob.sort(sortingContext, forceSort); pendingJob.requestSort(sortingContext, forceSort);
} }
else { else {
pendingJob = new SortJob<>(model, monitor, sortingContext, forceSort); pendingJob = new SortJob<>(model, monitor, sortingContext, forceSort);
@ -240,11 +240,11 @@ class ThreadedTableModelUpdateMgr<T> {
*/ */
void filter() { void filter() {
synchronized (updateManager) { synchronized (updateManager) {
if (currentJob != null && pendingJob == null && currentJob.filter()) { if (currentJob != null && pendingJob == null && currentJob.requestFilter()) {
return; return;
} }
if (pendingJob != null) { if (pendingJob != null) {
pendingJob.filter(); pendingJob.requestFilter();
} }
else { else {
pendingJob = new FilterJob<>(model, monitor); pendingJob = new FilterJob<>(model, monitor);
@ -297,7 +297,8 @@ class ThreadedTableModelUpdateMgr<T> {
/** /**
* Sets the delay for the swing update manager. * Sets the delay for the swing update manager.
* @param updateDelayMillis the new delay for the swing update manager. * @param updateDelayMillis the new delay for the swing update manager
* @param maxUpdateDelayMillis the new max update delay; updates will not wait past this time
*/ */
void setUpdateDelay(int updateDelayMillis, int maxUpdateDelayMillis) { void setUpdateDelay(int updateDelayMillis, int maxUpdateDelayMillis) {
updateManager.dispose(); updateManager.dispose();

View file

@ -21,6 +21,7 @@ import static org.junit.Assert.*;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Predicate;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@ -32,12 +33,15 @@ import ghidra.docking.spy.SpyEventRecorder;
import ghidra.framework.plugintool.ServiceProvider; import ghidra.framework.plugintool.ServiceProvider;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
public class ThreadedTableFilterTest extends AbstractThreadedTableTest { /**
* Specifically tests the sub-filtering behavior of the {@link ThreadedTableModel}, as well
* as some other more complicated filtering combinations
*/
public class DefaultThreadedTableFilterTest extends AbstractThreadedTableTest {
private SpyEventRecorder recorder = new SpyEventRecorder(getClass().getSimpleName()); private SpyEventRecorder recorder = new SpyEventRecorder(getClass().getSimpleName());
private SpyTaskMonitor monitor = new SpyTaskMonitor(); private SpyTaskMonitor monitor = new SpyTaskMonitor();
private SpyTextFilter<Long> spyFilter; private SpyTextFilter<Long> spyFilter;
private ThreadedTableModelListener spyLoadListener = new SpyTableModelListener();
@Override @Override
protected TestDataKeyModel createTestModel() { protected TestDataKeyModel createTestModel() {
@ -82,9 +86,11 @@ public class ThreadedTableFilterTest extends AbstractThreadedTableTest {
Boolean.FALSE.toString()); Boolean.FALSE.toString());
waitForTableModel(model); waitForTableModel(model);
}
// must run in Swing so that we do not mutate listeners while events are broadcasting @Override
runSwing(() -> model.addThreadedTableModelListener(spyLoadListener)); protected TestThreadedTableModelListener createListener() {
return new TestThreadedTableModelListener(model, recorder);
} }
@Override @Override
@ -98,7 +104,7 @@ public class ThreadedTableFilterTest extends AbstractThreadedTableTest {
@Test @Test
public void testRefilterHappensAfterAddItem_ItemAddedPassesFilter() throws Exception { public void testRefilterHappensAfterAddItem_ItemAddedPassesFilter() throws Exception {
int newRowIndex = model.getRowCount() + 1; int newRowIndex = getRowCount() + 1;
filterOnRawColumnValue(newRowIndex); filterOnRawColumnValue(newRowIndex);
resetSpies(); resetSpies();
@ -114,7 +120,7 @@ public class ThreadedTableFilterTest extends AbstractThreadedTableTest {
@Test @Test
public void testRefilterHappensAfterRemoveAddItem_ItemAddedPassesFilter() throws Exception { public void testRefilterHappensAfterRemoveAddItem_ItemAddedPassesFilter() throws Exception {
int newRowIndex = model.getRowCount() + 1; int newRowIndex = getRowCount() + 1;
filterOnRawColumnValue(newRowIndex); filterOnRawColumnValue(newRowIndex);
resetSpies(); resetSpies();
@ -137,7 +143,7 @@ public class ThreadedTableFilterTest extends AbstractThreadedTableTest {
@Test @Test
public void testRefilterHappensAfterAdd_ItemAddedFailsFilter() throws Exception { public void testRefilterHappensAfterAdd_ItemAddedFailsFilter() throws Exception {
int newRowIndex = model.getRowCount() + 1; int newRowIndex = getRowCount() + 1;
long nonMatchingFilter = 1; long nonMatchingFilter = 1;
filterOnRawColumnValue(nonMatchingFilter); filterOnRawColumnValue(nonMatchingFilter);
@ -370,6 +376,89 @@ public class ThreadedTableFilterTest extends AbstractThreadedTableTest {
assertRowCount(4); // matching values (for both filters): two, ten, ten, ten assertRowCount(4); // matching values (for both filters): two, ten, ten, ten
} }
@Test
public void testCombinedFilter_AddRemove_ItemPassesFilter_FilterJobStateDoesNotRun() {
//
// Tests that an item can be added/removed via addObject()/removeObject() *and* that,
// with a *combined* filter installed, the *filter* phase of the TableLoadJob will *NOT*
// get run. (The add/remove operation should perform filtering and sorting outside of
// the normal TableLoadJob's state machine.)
//
int fullCount = getRowCount();
createCombinedFilterWithEmptyTextFilter(new AllPassesTableFilter());
assertFilteredEntireModel();
assertRowCount(fullCount); // our filter passes everything
// call addObject()
long newId = fullCount + 1;
spyFilter.reset();
addItemToModel(newId);
assertNumberOfItemsPassedThroughFilter(1); // **this is the important check**
assertRowCount(fullCount + 1); // our filter passes everything
}
@Test
public void testCombinedFilter_AddRemove_ItemFailsFilter_FilterJobStateDoesNotRun() {
//
// Tests that an item can be added/removed via addObject()/removeObject() *and* that,
// with a *combined* filter installed, the *filter* phase of the TableLoadJob will *NOT*
// get run. (The add/remove operation should perform filtering and sorting outside of
// the normal TableLoadJob's state machine.)
//
int fullCount = getRowCount();
// use filter to limit any new items added from passing
Predicate<Long> predicate = l -> l < fullCount;
PredicateTableFilter noNewItemsPassFilter = new PredicateTableFilter(predicate);
createCombinedFilterWithEmptyTextFilter(noNewItemsPassFilter);
assertFilteredEntireModel();
assertRowCount(fullCount); // our filter passes everything
// call addObject()
long newId = fullCount + 1;
spyFilter.reset();
addItemToModel(newId);
assertNumberOfItemsPassedThroughFilter(1); // **this is the important check**
assertRowCount(fullCount); // the new item should not be added
}
@Test
public void testCombinedFilter_AddRemove_ItemPassesFilter_RefilterThenUndo() throws Exception {
//
// Bug Case: This was a case where a table (like the Symbol Table) that uses permanent
// combined filters would lose items inserted via the addObject() call. The
// issue is that the job was not properly updating the table's full source
// data, only its filtered data. Thus, when a job triggered a reload from
// the original source data, the value would be lost.
//
int fullCount = getRowCount();
createCombinedFilterWithEmptyTextFilter(new AllPassesTableFilter());
assertFilteredEntireModel();
assertRowCount(fullCount); // our filter passes everything
// call addObject()
long newId = fullCount + 1;
addItemToModel(newId);
assertRowCount(fullCount + 1); // our filter passes everything
filterOnRawColumnValue(newId);
assertRowCount(1);
createCombinedFilterWithEmptyTextFilter(new AllPassesTableFilter());
assertRowCount(fullCount + 1);
}
//================================================================================================== //==================================================================================================
// Private Methods // Private Methods
//================================================================================================== //==================================================================================================
@ -380,18 +469,18 @@ public class ThreadedTableFilterTest extends AbstractThreadedTableTest {
} }
private void assertFilteredEntireModel() { private void assertFilteredEntireModel() {
int allCount = model.getUnfilteredCount(); int allCount = getUnfilteredRowCount();
assertNumberOfItemsPassedThroughFilter(allCount); assertNumberOfItemsPassedThroughFilter(allCount);
} }
private void assertTableContainsValue(long expected) { private void assertTableContainsValue(long expected) {
List<Long> modelValues = model.getModelData(); List<Long> modelValues = getModelData();
assertTrue("Value not in the model--filtered out? - Expected " + expected + "; found " + assertTrue("Value not in the model--filtered out? - Expected " + expected + "; found " +
modelValues, modelValues.contains(expected)); modelValues, modelValues.contains(expected));
} }
private void assertTableDoesNotContainValue(long expected) { private void assertTableDoesNotContainValue(long expected) {
List<Long> modelValues = model.getModelData(); List<Long> modelValues = getModelData();
assertFalse("Value in the model--should not be there - Value " + expected + "; found " + assertFalse("Value in the model--should not be there - Value " + expected + "; found " +
modelValues, modelValues.contains(expected)); modelValues, modelValues.contains(expected));
} }
@ -449,6 +538,27 @@ public class ThreadedTableFilterTest extends AbstractThreadedTableTest {
waitForSwing(); waitForSwing();
} }
private void createCombinedFilterWithEmptyTextFilter(TableFilter<Long> nonTextFilter) {
// the row objects are Long values that are 0-based one-up index values
DefaultRowFilterTransformer<Long> transformer =
new DefaultRowFilterTransformer<>(model, table.getColumnModel());
TextFilter allPassesFilter = new EmptyTextFilter();
spyFilter = new SpyTextFilter<>(allPassesFilter, transformer, recorder);
CombinedTableFilter<Long> combinedFilter =
new CombinedTableFilter<>(spyFilter, nonTextFilter, null);
recorder.record("Before setting the new filter");
runSwing(() -> model.setTableFilter(combinedFilter));
recorder.record("\tafter setting filter");
waitForNotBusy();
waitForTableModel(model);
waitForSwing();
}
private void createCombinedStartsWithFilter(String filterValue, private void createCombinedStartsWithFilter(String filterValue,
TableFilter<Long> secondFilter) { TableFilter<Long> secondFilter) {
@ -464,9 +574,11 @@ public class ThreadedTableFilterTest extends AbstractThreadedTableTest {
spyFilter = new SpyTextFilter<>(textFilter, transformer, recorder); spyFilter = new SpyTextFilter<>(textFilter, transformer, recorder);
CombinedTableFilter<Long> combinedFilter = CombinedTableFilter<Long> combinedFilter =
new CombinedTableFilter<Long>(spyFilter, secondFilter, null); new CombinedTableFilter<>(spyFilter, secondFilter, null);
recorder.record("Before setting the new filter");
runSwing(() -> model.setTableFilter(combinedFilter)); runSwing(() -> model.setTableFilter(combinedFilter));
recorder.record("\tafter setting filter");
waitForNotBusy(); waitForNotBusy();
waitForTableModel(model); waitForTableModel(model);
@ -505,6 +617,10 @@ public class ThreadedTableFilterTest extends AbstractThreadedTableTest {
assertTrue("The table did not filter data when it should have", spyFilter.hasFiltered()); assertTrue("The table did not filter data when it should have", spyFilter.hasFiltered());
} }
@Override
protected void record(String message) {
recorder.record("Test - " + message);
}
//================================================================================================== //==================================================================================================
// Inner Classes // Inner Classes
//================================================================================================== //==================================================================================================
@ -568,28 +684,53 @@ public class ThreadedTableFilterTest extends AbstractThreadedTableTest {
} }
private class SpyTableModelListener implements ThreadedTableModelListener { private class EmptyTextFilter implements TextFilter {
@Override @Override
public void loadPending() { public boolean matches(String text) {
recorder.record("Swing - model load pending"); return true;
} }
@Override @Override
public void loadingStarted() { public String getFilterText() {
recorder.record("Swing - model load started"); return null;
} }
@Override @Override
public void loadingFinished(boolean wasCancelled) { public boolean isSubFilterOf(TextFilter filter) {
if (wasCancelled) { return true;
recorder.record("Swing - model load cancelled");
}
else {
recorder.record("Swing - model load finsished; size: " + model.getRowCount());
}
} }
} }
private class AllPassesTableFilter implements TableFilter<Long> {
@Override
public boolean acceptsRow(Long rowObject) {
return true;
}
@Override
public boolean isSubFilterOf(TableFilter<?> tableFilter) {
return false;
}
}
private class PredicateTableFilter implements TableFilter<Long> {
private Predicate<Long> predicate;
PredicateTableFilter(Predicate<Long> predicate) {
this.predicate = predicate;
}
@Override
public boolean acceptsRow(Long rowObject) {
return predicate.test(rowObject);
}
@Override
public boolean isSubFilterOf(TableFilter<?> tableFilter) {
return false;
}
}
} }

View file

@ -121,7 +121,7 @@ public class IncrementalThreadedTableTest extends AbstractThreadedTableTest {
@Override @Override
protected TestThreadedTableModelListener createListener() { protected TestThreadedTableModelListener createListener() {
return new TestIncrementalThreadedTableModelListener(spy); return new TestIncrementalThreadedTableModelListener(model, spy);
} }
//================================================================================================== //==================================================================================================

View file

@ -0,0 +1,34 @@
/* ###
* IP: GHIDRA
*
* 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.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package docking.widgets.table.threaded;
import org.junit.Before;
import docking.widgets.table.TableSortState;
public class NonSortedThreadedTableFilterTest extends DefaultThreadedTableFilterTest {
@Override
@Before
public void setUp() throws Exception {
super.setUp();
TableSortState sortState = TableSortState.createUnsortedSortState();
runSwing(() -> model.setTableSortState(sortState));
waitForTableModel(model);
}
}

View file

@ -0,0 +1,35 @@
/* ###
* IP: GHIDRA
*
* 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.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package docking.widgets.table.threaded;
import org.junit.Before;
import docking.widgets.table.TableSortState;
public class NonSortedThreadedTableTest extends ThreadedTableTest {
@Override
@Before
public void setUp() throws Exception {
super.setUp();
TableSortState sortState = TableSortState.createUnsortedSortState();
runSwing(() -> model.setTableSortState(sortState));
waitForTableModel(model);
}
}

View file

@ -29,8 +29,7 @@ public class SpyTextFilter<T> extends TableTextFilter<T> {
private SpyEventRecorder recorder; private SpyEventRecorder recorder;
SpyTextFilter(TextFilter textFilter, RowFilterTransformer<T> transformer) { SpyTextFilter(TextFilter textFilter, RowFilterTransformer<T> transformer) {
super(textFilter, transformer); this(textFilter, transformer, new SpyEventRecorder("Stub"));
this.recorder = new SpyEventRecorder("Stub");
} }
SpyTextFilter(TextFilter textFilter, RowFilterTransformer<T> transformer, SpyTextFilter(TextFilter textFilter, RowFilterTransformer<T> transformer,

View file

@ -45,7 +45,7 @@ public class ThreadedTableTest extends AbstractThreadedTableTest {
private final Pattern SORT_SIZE_PATTERN = Pattern.compile(".*\\((\\d+) rows\\).*"); private final Pattern SORT_SIZE_PATTERN = Pattern.compile(".*\\((\\d+) rows\\).*");
private SpyEventRecorder recorder = new SpyEventRecorder(getClass().getSimpleName()); private SpyEventRecorder recorder = new SpyEventRecorder(testName.getMethodName());
private SpyTaskMonitor spyMonitor = new SpyTaskMonitor(recorder); private SpyTaskMonitor spyMonitor = new SpyTaskMonitor(recorder);
private SpyTextFilter<Long> spyFilter; private SpyTextFilter<Long> spyFilter;
private SortListener spySortListener = private SortListener spySortListener =
@ -102,6 +102,12 @@ public class ThreadedTableTest extends AbstractThreadedTableTest {
public void setIncrementalTaskMonitor(TaskMonitor monitor) { public void setIncrementalTaskMonitor(TaskMonitor monitor) {
// no! some of our tests use a spy monitor // no! some of our tests use a spy monitor
} }
@Override
public void setTableSortState(TableSortState newSortState) {
record("model.setTableSortState() - " + newSortState);
super.setTableSortState(newSortState);
}
}); });
return box[0]; return box[0];
} }
@ -111,7 +117,6 @@ public class ThreadedTableTest extends AbstractThreadedTableTest {
// //
// make sure the sort is on the filtered data and not *all* data // make sure the sort is on the filtered data and not *all* data
// //
waitForNotBusy();
assertSortSize(12); assertSortSize(12);
filter_ten(); filter_ten();
@ -291,8 +296,8 @@ public class ThreadedTableTest extends AbstractThreadedTableTest {
@Test @Test
public void testAddSendsEvent() { public void testAddSendsEvent() {
waitForTableModel(model); waitForTableModel(model);
final AtomicReference<TableModelEvent> ref = new AtomicReference<>(); AtomicReference<TableModelEvent> ref = new AtomicReference<>();
model.addTableModelListener(e -> ref.set(e)); runSwing(() -> model.addTableModelListener(e -> ref.set(e)));
int newValue = model.getRowCount() + 1; int newValue = model.getRowCount() + 1;
addItemToModel(newValue); addItemToModel(newValue);
@ -512,7 +517,6 @@ public class ThreadedTableTest extends AbstractThreadedTableTest {
// //
// we need to use a model that loads slowly enough to trigger the pending panel to show // we need to use a model that loads slowly enough to trigger the pending panel to show
// //
waitForNotBusy();// if we are working while adding, we will not get the pending notification
model.setDelayTimeBetweenAddingDataItemsWhileLoading(60000); model.setDelayTimeBetweenAddingDataItemsWhileLoading(60000);
model.setUpdateDelay(100000000, 100000001);// make sure we don't update after repeated requests arrive model.setUpdateDelay(100000000, 100000001);// make sure we don't update after repeated requests arrive
@ -534,15 +538,15 @@ public class ThreadedTableTest extends AbstractThreadedTableTest {
@Override @Override
protected void sortByClick(int columnToClick, int modifiers) throws Exception { protected void sortByClick(int columnToClick, int modifiers) throws Exception {
recorder.record( recorder.record("Test." + testName.getMethodName() + " - clicking column=" + columnToClick +
"Test." + testName + " - clicking column=" + columnToClick + "; modifiers=" + 0); "; modifiers=" + 0);
super.sortByClick(columnToClick, modifiers); super.sortByClick(columnToClick, modifiers);
} }
@Override @Override
protected void removeSortByClicking(int columnToClick) throws Exception { protected void removeSortByClicking(int columnToClick) throws Exception {
recorder.record( recorder.record("Test." + testName.getMethodName() +
"Test." + testName + " - clicking column to remove sort - column=" + columnToClick); " - clicking column to remove sort - column=" + columnToClick);
super.removeSortByClicking(columnToClick); super.removeSortByClicking(columnToClick);
} }
@ -607,7 +611,7 @@ public class ThreadedTableTest extends AbstractThreadedTableTest {
private void toggleStringColumnSort() throws Exception { private void toggleStringColumnSort() throws Exception {
Rectangle rect = header.getHeaderRect(TestDataKeyModel.STRING_COL); Rectangle rect = header.getHeaderRect(TestDataKeyModel.STRING_COL);
testTableModelListener.reset(model); resetBusyListener();
clickMouse(header, MouseEvent.BUTTON1, rect.x + 10, rect.y + 10, 1, 0); clickMouse(header, MouseEvent.BUTTON1, rect.x + 10, rect.y + 10, 1, 0);
waitForNotBusy(); waitForNotBusy();
waitForSwing(); waitForSwing();
@ -753,6 +757,8 @@ public class ThreadedTableTest extends AbstractThreadedTableTest {
} }
private void clearFilter() throws Exception { private void clearFilter() throws Exception {
resetSpies();
resetBusyListener();
runSwing(() -> model.setTableFilter(null)); runSwing(() -> model.setTableFilter(null));
waitForNotBusy(); waitForNotBusy();
@ -782,6 +788,7 @@ public class ThreadedTableTest extends AbstractThreadedTableTest {
@Override @Override
protected void doTestSorting(int columnIndex) throws Exception { protected void doTestSorting(int columnIndex) throws Exception {
sortByNormalClicking(columnIndex); sortByNormalClicking(columnIndex);
SortedTableModel sortedModel = (SortedTableModel) table.getModel(); SortedTableModel sortedModel = (SortedTableModel) table.getModel();
@ -829,7 +836,7 @@ public class ThreadedTableTest extends AbstractThreadedTableTest {
TextFilterFactory textFactory = options.getTextFilterFactory(); TextFilterFactory textFactory = options.getTextFilterFactory();
TextFilter textFilter = textFactory.getTextFilter(text); TextFilter textFilter = textFactory.getTextFilter(text);
testTableModelListener.reset(model); resetBusyListener();
spyFilter = new SpyTextFilter<>(textFilter, transformer, recorder); spyFilter = new SpyTextFilter<>(textFilter, transformer, recorder);
@ -859,6 +866,11 @@ public class ThreadedTableTest extends AbstractThreadedTableTest {
assertTrue("Table did not filter; requested filter on '" + text + "'", hasFiltered); assertTrue("Table did not filter; requested filter on '" + text + "'", hasFiltered);
} }
@Override
protected void record(String message) {
recorder.record("Test - " + message);
}
private void resetSpies() { private void resetSpies() {
spyFilter.reset(); spyFilter.reset();
spyMonitor.clearMessages(); spyMonitor.clearMessages();

View file

@ -59,13 +59,12 @@ public abstract class AbstractThreadedTableTest extends AbstractDockingTest {
buildFrame(); buildFrame();
}); });
} }
protected abstract TestDataKeyModel createTestModel(); protected abstract TestDataKeyModel createTestModel();
protected TestThreadedTableModelListener createListener() { protected TestThreadedTableModelListener createListener() {
return new TestThreadedTableModelListener(); return new TestThreadedTableModelListener(model);
} }
@After @After
@ -90,12 +89,12 @@ public abstract class AbstractThreadedTableTest extends AbstractDockingTest {
} }
protected void addItemToModel(long value) { protected void addItemToModel(long value) {
model.addObject(new Long(value)); model.addObject(Long.valueOf(value));
waitForTableModel(model); waitForTableModel(model);
} }
protected void removeItemFromModel(int value) { protected void removeItemFromModel(int value) {
model.removeObject(new Long(value)); model.removeObject(Long.valueOf(value));
waitForTableModel(model); waitForTableModel(model);
} }
@ -161,10 +160,20 @@ public abstract class AbstractThreadedTableTest extends AbstractDockingTest {
SortedTableModel sortedModel = (SortedTableModel) table.getModel(); SortedTableModel sortedModel = (SortedTableModel) table.getModel();
TableSortState sortState = getSortState(sortedModel); TableSortState sortState = getSortState(sortedModel);
ColumnSortState originalColumnSortState = sortState.iterator().next(); record("sortByClick() - initial sort state: " + sortState);
int currentSortedIndex = originalColumnSortState.getColumnModelIndex();
boolean checkSortDirection = (columnToClick == currentSortedIndex); int currentSortColunn = -1;
boolean isAscending = originalColumnSortState.isAscending(); boolean isAscending = true;
boolean checkSortDirection = false;
if (!sortState.isUnsorted()) {
// check to see if the tests is clicking the same column twice (to change the
// sort direction)
ColumnSortState originalColumnSortState = sortState.iterator().next();
currentSortColunn = originalColumnSortState.getColumnModelIndex();
checkSortDirection = (columnToClick == currentSortColunn);
isAscending = originalColumnSortState.isAscending();
}
testTableModelListener.reset(model); testTableModelListener.reset(model);
Rectangle rect = header.getHeaderRect(columnToClick); Rectangle rect = header.getHeaderRect(columnToClick);
@ -172,10 +181,14 @@ public abstract class AbstractThreadedTableTest extends AbstractDockingTest {
waitForPostedSwingRunnables(); waitForPostedSwingRunnables();
} }
record("Clicking table at column " + columnToClick);
clickMouse(header, MouseEvent.BUTTON1, rect.x + 10, rect.y + 10, 1, modifiers); clickMouse(header, MouseEvent.BUTTON1, rect.x + 10, rect.y + 10, 1, modifiers);
waitForNotBusy(); waitForNotBusy();
record("\tafter click; table not busy");
sortState = getSortState(sortedModel); sortState = getSortState(sortedModel);
record("Updated sort state: " + sortState);
ColumnSortState columnSortState = sortState.iterator().next(); ColumnSortState columnSortState = sortState.iterator().next();
int sortedIndex = columnSortState.getColumnModelIndex(); int sortedIndex = columnSortState.getColumnModelIndex();
verifyColumnSorted(sortedIndex, sortState); verifyColumnSorted(sortedIndex, sortState);
@ -188,10 +201,8 @@ public abstract class AbstractThreadedTableTest extends AbstractDockingTest {
} }
} }
protected TableSortState getSortState(final SortedTableModel sortedModel) { protected TableSortState getSortState(SortedTableModel sortedModel) {
final TableSortState[] box = new TableSortState[1]; return runSwing(() -> sortedModel.getTableSortState());
runSwing(() -> box[0] = sortedModel.getTableSortState());
return box[0];
} }
protected void removeSortByClicking(int columnToClick) throws Exception { protected void removeSortByClicking(int columnToClick) throws Exception {
@ -212,26 +223,41 @@ public abstract class AbstractThreadedTableTest extends AbstractDockingTest {
assertNotNull(columnSortState); assertNotNull(columnSortState);
} }
protected void resetBusyListener() {
testTableModelListener.reset(model);
}
protected void waitForNotBusy() { protected void waitForNotBusy() {
sleep(50); sleep(50);
int nWaits = 0; waitForCondition(() -> testTableModelListener.doneWork(),
int maxWaits = 500; "Timed-out waiting for table model to update.");
while (!testTableModelListener.doneWork() && nWaits++ < maxWaits) {
sleep(50);
}
assertTrue("Timed-out waiting for table model to update.", nWaits < maxWaits);
waitForSwing(); waitForSwing();
} }
protected void addLong(final long value) { protected void addLong(final long value) {
runSwing(() -> model.addObject(new Long(value))); runSwing(() -> model.addObject(Long.valueOf(value)));
}
protected int getRowCount() {
return runSwing(() -> model.getRowCount());
}
protected int getUnfilteredRowCount() {
return runSwing(() -> model.getUnfilteredRowCount());
}
protected List<Long> getModelData() {
return runSwing(() -> model.getModelData());
}
protected void record(String message) {
// no-op for base class; subclasses know how to record debug
} }
protected void assertRowCount(int expectedCount) { protected void assertRowCount(int expectedCount) {
int rowCount = model.getRowCount(); int rowCount = model.getRowCount();
assertThat("Have different number of table rows than expected after filtering", assertThat("Have different number of table rows than expected after filtering", rowCount,
expectedCount, is(rowCount)); is(expectedCount));
} }
protected void assertNoRowsFilteredOut() { protected void assertNoRowsFilteredOut() {

View file

@ -19,8 +19,9 @@ import ghidra.docking.spy.SpyEventRecorder;
public class TestIncrementalThreadedTableModelListener extends TestThreadedTableModelListener { public class TestIncrementalThreadedTableModelListener extends TestThreadedTableModelListener {
TestIncrementalThreadedTableModelListener(SpyEventRecorder spy) { TestIncrementalThreadedTableModelListener(ThreadedTableModel<?, ?> model,
super(spy); SpyEventRecorder spy) {
super(model, spy);
} }
@Override @Override

View file

@ -25,46 +25,49 @@ public class TestThreadedTableModelListener implements ThreadedTableModelListene
private volatile boolean cancelled; private volatile boolean cancelled;
private SpyEventRecorder spy; private SpyEventRecorder spy;
private ThreadedTableModel<?, ?> model;
public TestThreadedTableModelListener() { public TestThreadedTableModelListener(ThreadedTableModel<?, ?> model) {
this(new SpyEventRecorder("Listener Spy")); this(model, new SpyEventRecorder("Listener Spy"));
} }
public TestThreadedTableModelListener(SpyEventRecorder spy) { public TestThreadedTableModelListener(ThreadedTableModel<?, ?> model, SpyEventRecorder spy) {
this.spy = spy; this.spy = spy;
this.model = model;
} }
void reset(ThreadedTableModel<?, ?> model) { void reset(ThreadedTableModel<?, ?> newModel) {
spy.record("listener - reset()"); spy.record("Test - listener - reset()");
completed = cancelled = false; completed = cancelled = false;
} }
boolean doneWork() { boolean doneWork() {
spy.record("listener - doneWork()? " + (completed || cancelled) + " - complted? " + spy.record("Test - listener - doneWork()? " + (completed || cancelled) + " - completed? " +
completed + "; cancelled? " + cancelled); completed + "; cancelled? " + cancelled);
return completed || cancelled; return completed || cancelled;
} }
boolean startedWork() { boolean startedWork() {
spy.record("listener - startedWork() - updating? " + updating); spy.record("Test - listener - startedWork() - updating? " + updating);
return updating; return updating;
} }
@Override @Override
public void loadPending() { public void loadPending() {
spy.record("listener - loadPending()"); spy.record("Swing - listener - loadPending()");
pending = true; pending = true;
} }
@Override @Override
public void loadingStarted() { public void loadingStarted() {
spy.record("listener - loadStarted()"); spy.record("Swing - listener - loadStarted()");
updating = true; updating = true;
} }
@Override @Override
public void loadingFinished(boolean wasCancelled) { public void loadingFinished(boolean wasCancelled) {
spy.record("listener - loadingFinished() - cancelled? " + wasCancelled); spy.record("Swing - listener - loadingFinished() - cancelled? " + wasCancelled +
"; size: " + model.getRowCount());
cancelled = wasCancelled; cancelled = wasCancelled;
completed = !cancelled; completed = !cancelled;
} }

View file

@ -17,6 +17,7 @@ package ghidra.docking.spy;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.commons.lang3.time.FastDateFormat; import org.apache.commons.lang3.time.FastDateFormat;
@ -33,28 +34,55 @@ public class SpyEventRecorder {
private String recorderName; private String recorderName;
private List<SpyEvent> events = new ArrayList<>(); private List<SpyEvent> events = new ArrayList<>();
private AtomicBoolean buffered = new AtomicBoolean(true);
public SpyEventRecorder(String recorderName) { public SpyEventRecorder(String recorderName) {
this.recorderName = recorderName; this.recorderName = recorderName;
} }
public void setBuffered(boolean buffered) {
this.buffered.set(buffered);
}
// synchronized because we spy on multiple threads (like Test and Swing) // synchronized because we spy on multiple threads (like Test and Swing)
public synchronized void record(String message) { public synchronized void record(String message) {
SpyEvent event = new SpyEvent(message); SpyEvent event = new SpyEvent(message);
events.add(event);
if (buffered.get()) {
events.add(event);
}
else {
// System.err intentional here for aesthetics
System.err.println(event.toString(0));
}
}
private synchronized String eventsToString() {
int size = events.size();
int length = Integer.toString(size).length();
StringBuilder buffy = new StringBuilder("Recorded Events - " + recorderName + '\n');
for (SpyEvent event : events) {
buffy.append(event.toString(length)).append('\n');
}
return buffy.toString();
} }
// synchronized because we spy on multiple threads (like Test and Swing) // synchronized because we spy on multiple threads (like Test and Swing)
public synchronized void dumpEvents() { public void dumpEvents() {
StringBuilder buffy = new StringBuilder("Recorded Events - " + recorderName + '\n'); Msg.debug(this, eventsToString());
for (SpyEvent event : events) { }
buffy.append(event.toString()).append('\n');
} @Override
Msg.debug(this, buffy.toString()); public String toString() {
return eventsToString();
} }
private class SpyEvent { private class SpyEvent {
private FastDateFormat dateFormat = FastDateFormat.getInstance("'T'HH:mm:ssZZ"); private static final String PADDING = " ";
private FastDateFormat dateFormat = FastDateFormat.getInstance("'T'HH:mm:ss:SSS");
private int id; private int id;
private String message; private String message;
@ -65,9 +93,13 @@ public class SpyEventRecorder {
this.id = ++globalId; this.id = ++globalId;
} }
@Override String toString(int idPad) {
public String toString() {
return "(" + id + ") " + dateFormat.format(time) + " " + message; int myLength = Integer.toString(id).length();
int delta = Math.max(0, idPad - myLength);
String pad = PADDING.substring(0, delta);
return "(" + id + ") " + pad + dateFormat.format(time) + " " + message;
} }
} }
} }

View file

@ -96,7 +96,7 @@ public class ResourceManager {
return is; return is;
} }
URL url = getResource(testSearchPaths, filename); URL url = getResource(getTestSearchPaths(), filename);
if (url == null) { if (url == null) {
return null; return null;
} }