GP-1981 - Updated help to allow authors to use theme image ids; updated

help to only allow modules that live on the classpath. Dpdating themd
documentation. Fixing gradle help build
This commit is contained in:
ghidragon
2022-10-20 14:09:21 -04:00
parent c86b884daf
commit 45c52e3cb9
105 changed files with 1660 additions and 569 deletions

View File

@@ -18,10 +18,10 @@ package help;
import java.awt.Component;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Hashtable;
import java.util.Locale;
import java.util.*;
import javax.help.*;
import javax.help.Map;
import javax.help.Map.ID;
import javax.help.event.HelpModelEvent;
import javax.help.plaf.HelpNavigatorUI;
@@ -143,6 +143,8 @@ public class CustomTOCView extends TOCView {
* Our hook to install our custom cell renderer.
*/
class CustomTOCNavigatorUI extends BasicTOCNavigatorUI {
private static final String ROOT_TOC_ID = "Root";
public CustomTOCNavigatorUI(JHelpTOCNavigator b) {
super(b);
}
@@ -184,27 +186,42 @@ public class CustomTOCView extends TOCView {
}
TOCItem item = (TOCItem) treeItem;
ID itemID = item.getID();
if (itemID == null) {
Msg.debug(this, "No help ID for " + item);
return;
}
String presentation = item.getPresentation();
if (presentation != null) {
return; // don't currently support presentations
}
CustomTreeItemDecorator customItem = (CustomTreeItemDecorator) item;
ID itemId = getId(customItem, helpModel);
if (itemId == null) {
Msg.debug(this, "No help ID for " + item);
return;
}
String customDisplayText = customItem.getDisplayText();
try {
helpModel.setCurrentID(itemID, customDisplayText, navigator);
helpModel.setCurrentID(itemId, customDisplayText, navigator);
}
catch (InvalidHelpSetContextException ex) {
Msg.error(this, "Exception setting new help item ID", ex);
}
}
private ID getId(CustomTreeItemDecorator item, HelpModel helpModel) {
ID itemId = item.getID();
if (itemId != null) {
return itemId;
}
String tocID = item.getTocID();
if (Objects.equals(tocID, ROOT_TOC_ID)) {
HelpSet hs = helpModel.getHelpSet();
return hs.getHomeID();
}
return null;
}
private TOCItem getSelectedItem(TreeSelectionEvent e, JHelpNavigator navigator) {
TreePath newLeadSelectionPath = e.getNewLeadSelectionPath();
if (newLeadSelectionPath == null) {

View File

@@ -21,7 +21,7 @@ import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import ghidra.GhidraApplicationLayout;
import generic.theme.Gui;
import ghidra.framework.Application;
import ghidra.framework.ApplicationConfiguration;
import help.validator.*;
@@ -55,8 +55,8 @@ public class GHelpBuilder {
private String outputDirectoryName;
private String moduleName;
private Collection<File> dependencyHelpPaths = new LinkedHashSet<File>();
private Collection<File> helpInputDirectories = new LinkedHashSet<File>();
private Collection<File> dependencyHelpPaths = new LinkedHashSet<>();
private Collection<File> helpInputDirectories = new LinkedHashSet<>();
private static boolean debugEnabled = false;
private boolean ignoreInvalid = false; // TODO: Do actual validation here
@@ -66,9 +66,18 @@ public class GHelpBuilder {
public static void main(String[] args) throws Exception {
GHelpBuilder builder = new GHelpBuilder();
builder.exitOnError = true;
ApplicationConfiguration config = new ApplicationConfiguration() {
@Override
protected void initializeApplication() {
Gui.initialize();
}
ApplicationConfiguration config = new ApplicationConfiguration();
Application.initializeApplication(new GhidraApplicationLayout(), config);
@Override
public boolean isHeadless() {
return false;
}
};
Application.initializeApplication(new HelpApplicationLayout("Help Builder", "0.1"), config);
builder.build(args);
}
@@ -98,7 +107,7 @@ public class GHelpBuilder {
}
private HelpModuleCollection collectAllHelp() {
List<File> allHelp = new ArrayList<File>(helpInputDirectories);
List<File> allHelp = new ArrayList<>(helpInputDirectories);
for (File file : dependencyHelpPaths) {
allHelp.add(file);
}

View File

@@ -34,6 +34,8 @@ import javax.swing.text.html.*;
import javax.swing.text.html.HTML.Tag;
import generic.jar.ResourceFile;
import generic.theme.GIcon;
import generic.theme.Gui;
import ghidra.framework.Application;
import ghidra.framework.preferences.Preferences;
import ghidra.util.Msg;
@@ -480,6 +482,12 @@ public class GHelpHTMLEditorKit extends HTMLEditorKit {
}
String srcString = src.toString();
//check if the icon is a defined icon theme id
if (Gui.hasIcon(srcString)) {
return new GIcon(srcString).getUrl();
}
if (isJavaCode(srcString)) {
return installImageFromJavaCode(srcString);
}

View File

@@ -54,8 +54,6 @@ import ghidra.util.SystemUtilities;
*/
public class GHelpSet extends HelpSet {
private static final String HOME_ID = "Misc_Welcome_to_Ghidra_Help";
/** <b>static</b> map that contains all known help sets in the system. */
private static java.util.Map<HelpSet, Map> helpSetsToCombinedMaps = new java.util.HashMap<>();
private static java.util.Map<HelpSet, Map> helpSetsToLocalMaps = new java.util.HashMap<>();
@@ -79,8 +77,6 @@ public class GHelpSet extends HelpSet {
setKeyData(kitTypeRegistry, type, editorKit);
setKeyData(kitLoaderRegistry, type, classLoader);
setHomeID(HOME_ID);
initializeCombinedMapWrapper();
}
@@ -121,6 +117,11 @@ public class GHelpSet extends HelpSet {
}
}
@Override
public String toString() {
return getHelpSetURL().toString();
}
//==================================================================================================
// Inner Classes
//==================================================================================================

View File

@@ -0,0 +1,154 @@
/* ###
* 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 help;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.*;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import generic.jar.ResourceFile;
import ghidra.framework.ApplicationProperties;
import ghidra.framework.GModule;
import ghidra.util.SystemUtilities;
import util.CollectionUtils;
import utility.application.ApplicationLayout;
import utility.application.ApplicationUtilities;
import utility.module.ClasspathFilter;
import utility.module.ModuleUtilities;
//
// TODO this class should be deleted when the GP-1981 branch is merged into master. The
// DockingApplicationLayout is not accessible by help, due to Docking depending on Help.
// Much of the application layout code should live in Utility so that it is reachable by more
// modules. Docking and Ghidra application layout classes should also have their names
// changed.
//
// Perhaps: ModuleAppliactionLayout and ClasspathApplicationLayout.
//
public class HelpApplicationLayout extends ApplicationLayout {
private static final String NO_RELEASE_NAME = "NO_RELEASE";
/** Dev mode main source bin dir pattern */
private static final Pattern CLASS_PATH_MODULE_NAME_PATTERN =
Pattern.compile(".*/(\\w+)/bin/main");
/**
* Constructs a new docking application layout object with the given name and version.
*
* @param name The name of the application.
* @param version The version of the application.
* @throws FileNotFoundException if there was a problem getting a user directory.
*/
public HelpApplicationLayout(String name, String version) throws FileNotFoundException {
this.applicationProperties =
Objects.requireNonNull(new ApplicationProperties(name, version, NO_RELEASE_NAME));
this.applicationRootDirs = getDefaultApplicationRootDirs();
applicationRootDirs.addAll(getAdditionalApplicationRootDirs(applicationRootDirs));
// Application installation directory
applicationInstallationDir = applicationRootDirs.iterator().next().getParentFile();
if (SystemUtilities.isInDevelopmentMode()) {
applicationInstallationDir = applicationInstallationDir.getParentFile();
}
// Modules
if (SystemUtilities.isInDevelopmentMode()) {
// In development mode we rely on the IDE's classpath to determine which modules to
// include, as opposed to scanning the filesystem. This prevents unrelated modules
// from being used.
modules = ModuleUtilities.findModules(applicationRootDirs,
ModuleUtilities.findModuleRootDirectories(applicationRootDirs),
new ClasspathFilter());
}
else {
modules = ModuleUtilities.findModules(applicationRootDirs, applicationRootDirs);
}
// User directories
userTempDir = ApplicationUtilities.getDefaultUserTempDir(applicationProperties);
userSettingsDir = ApplicationUtilities.getDefaultUserSettingsDir(applicationProperties,
applicationInstallationDir);
}
protected Collection<ResourceFile> getAdditionalApplicationRootDirs(
Collection<ResourceFile> roots) {
return Collections.emptyList();
}
protected Map<String, GModule> findModules() {
if (!SystemUtilities.isInDevelopmentMode()) {
// in release mode we only have one application root, so no need to find all others
return ModuleUtilities.findModules(applicationRootDirs, applicationRootDirs);
}
// In development mode we may have multiple module root directories under which modules may
// be found. Search all roots for modules.
Collection<ResourceFile> roots =
ModuleUtilities.findModuleRootDirectories(applicationRootDirs, new ArrayList<>());
Map<String, GModule> allModules = ModuleUtilities.findModules(applicationRootDirs, roots);
// Filter any modules found to ensure that we only include those that are listed on the
// classpath. (Due to the nature of how the development classpath is created, not all
// found modules may match the classpath entries.)
Set<String> cpNames = getClassPathModuleNames();
Map<String, GModule> filteredModules = new HashMap<>();
Set<Entry<String, GModule>> entrySet = allModules.entrySet();
for (Entry<String, GModule> entry : entrySet) {
GModule module = entry.getValue();
if (cpNames.contains(module.getName())) {
filteredModules.put(entry.getKey(), module);
}
}
return filteredModules;
}
private Set<String> getClassPathModuleNames() {
String cp = System.getProperty("java.class.path");
String[] pathParts = cp.split(File.pathSeparator);
Set<String> paths = new HashSet<>(Arrays.asList(pathParts));
Set<String> cpNames = new HashSet<>();
for (String cpEntry : paths) {
Matcher matcher = CLASS_PATH_MODULE_NAME_PATTERN.matcher(cpEntry);
if (matcher.matches()) {
cpNames.add(matcher.group(1));
}
}
return cpNames;
}
/**
* Get the default list of Application directories. In repo-based
* development mode this includes the root Ghidra directory within each repo.
* When not in development mode, the requirement is that the current working
* directory correspond to the installation root. The first entry will be
* the primary root in both cases.
* @return root directories
*/
public static Collection<ResourceFile> getDefaultApplicationRootDirs() {
if (SystemUtilities.isInDevelopmentMode()) {
return ApplicationUtilities.findDefaultApplicationRootDirs();
}
return CollectionUtils.asList(new ResourceFile(System.getProperty("user.dir")));
}
}

View File

@@ -24,6 +24,8 @@ import java.util.Collections;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import generic.theme.GIcon;
import generic.theme.Gui;
import help.validator.location.*;
import resources.IconProvider;
import resources.Icons;
@@ -73,9 +75,12 @@ public class HelpBuildUtils {
}
/**
* Returns a file object that is the help topic directory for the given file.
* This method is useful for finding the help topic directory when the given
* file doesn't live directly under a help topic.
* Returns a file object that is the help topic directory for the given file.
*
* <p>This method is useful for finding the help topic directory when the given file doesn't
* live directly under a help topic.
* @param file the file for which to find a topic
* @return the path to the help topic directory
*/
public static Path getHelpTopicDir(Path file) {
Path helpTopics = file.getFileSystem().getPath("help", "topics");
@@ -540,7 +545,7 @@ public class HelpBuildUtils {
* @param sourceFile the source file path of the image reference
* @param ref the reference text
* @return an absolute path; null if the URI is remote
* @throws URISyntaxException
* @throws URISyntaxException if there is an exception creating a URL/URI for the image location
*/
public static ImageLocation locateImageReference(Path sourceFile, String ref)
throws URISyntaxException {
@@ -563,6 +568,13 @@ public class HelpBuildUtils {
}
return ImageLocation.createRuntimeLocation(sourceFile, ref, resolved, path);
}
if (Gui.hasIcon(ref)) {
GIcon gIcon = new GIcon(ref);
URL url = gIcon.getUrl();
URI resolved = url.toURI();
Path path = toPath(resolved);
return ImageLocation.createRuntimeLocation(sourceFile, ref, resolved, path);
}
URI resolved = resolve(sourceFile, ref);
if (isRemote(resolved)) {
@@ -574,13 +586,14 @@ public class HelpBuildUtils {
}
/**
* Turn an HTML HREF reference into an absolute path. This will
* locate files based upon relative references, specialized help system references (i.e.,
* help/topics/...), and absolute URLs.
* Turn an HTML HREF reference into an absolute path. This will locate files based upon
* relative references, specialized help system references (i.e., help/topics/...), and
* absolute URLs.
*
* @param sourceFile the reference's source file
* @param ref the reference text
* @return an absolute path; null if the URI is remote
* @throws URISyntaxException
* @throws URISyntaxException if there is an exception creating a URL/URI for the image location
*/
public static Path locateReference(Path sourceFile, String ref) throws URISyntaxException {

View File

@@ -17,12 +17,19 @@ package help;
import static org.junit.Assert.*;
import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.file.Path;
import java.nio.file.Paths;
import org.junit.Test;
import generic.theme.Gui;
import ghidra.GhidraTestApplicationLayout;
import ghidra.framework.ApplicationConfiguration;
import utility.application.ApplicationLayout;
public class HelpBuildUtilsTest extends AbstractHelpTest {
private static final String HELP_TOPIC_PATH = "/some/fake/path/to/help/topics";
@@ -33,6 +40,22 @@ public class HelpBuildUtilsTest extends AbstractHelpTest {
super();
}
@Override
protected ApplicationLayout createApplicationLayout() throws IOException {
return new GhidraTestApplicationLayout(new File(getTestDirectoryPath()));
}
@Override
protected ApplicationConfiguration createApplicationConfiguration() {
ApplicationConfiguration configuration = new ApplicationConfiguration() {
@Override
public boolean isHeadless() {
return false;
}
};
return configuration;
}
@Test
public void testGetRelativeHelpPath() {
String relativeString = "help/topics/FooTopic/FooFile.html";
@@ -78,6 +101,7 @@ public class HelpBuildUtilsTest extends AbstractHelpTest {
@Test
public void testLocateReferences_Icons() throws URISyntaxException {
Gui.initialize();
Path sourceFile = Paths.get(HTML_FILE_PATH);
String reference = "Icons.REFRESH_ICON"; // see Icons class
ImageLocation location = HelpBuildUtils.locateImageReference(sourceFile, reference);