mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-05 10:49:34 +02:00
Merge remote-tracking branch 'origin/GT-2763-dragonmacher-unsort-tables'
This commit is contained in:
commit
c048022308
72 changed files with 1453 additions and 1201 deletions
|
@ -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|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
|
@ -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");
|
||||||
|
|
|
@ -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 + ")" : ""));
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)";
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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> {
|
||||||
|
|
||||||
|
|
|
@ -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));
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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");
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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));
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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() + "'";
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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)) {
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
//==================================================================================================
|
//==================================================================================================
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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,
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue