Updated module system so Help no longer depends on Docking. Docking can now have help content.

This commit is contained in:
dragonmacher 2022-09-16 12:21:32 -04:00
parent a438a1e1ea
commit cb02db8313
87 changed files with 707 additions and 445 deletions

View file

@ -15,6 +15,7 @@
*/
apply from: "$rootProject.projectDir/gradle/distributableGhidraModule.gradle"
apply from: "$rootProject.projectDir/gradle/javaProject.gradle"
apply from: "$rootProject.projectDir/gradle/helpProject.gradle"
apply from: "$rootProject.projectDir/gradle/jacocoProject.gradle"
apply from: "$rootProject.projectDir/gradle/javaTestProject.gradle"
apply from: "$rootProject.projectDir/gradle/javadoc.gradle"
@ -24,12 +25,8 @@ eclipse.project.name = 'Framework Docking'
dependencies {
api project(':Generic')
api 'net.java.dev.timingframework:timingframework:1.0'
// Only include this debug version of the jh library if necessary.
//api name:'jh2.with.debug'
api 'javax.help:javahelp:2.0.05'
api project(':Help')
// include code from src/test in Generic
testImplementation project(path: ':Generic', configuration: 'testArtifacts')

View file

@ -10,6 +10,18 @@
##MODULE IP: Tango Icons - Public Domain
Module.manifest||GHIDRA||||END|
data/ExtensionPoint.manifest||GHIDRA||||END|
src/main/help/help/TOC_Source.xml||GHIDRA||||END|
src/main/help/help/shared/arrow.gif||GHIDRA||||END|
src/main/help/help/shared/close16.gif||GHIDRA||||END|
src/main/help/help/shared/menu16.gif||GHIDRA||||END|
src/main/help/help/shared/note-red.png||Oxygen Icons - LGPL 3.0|||Oxygen icon theme (dual license; LGPL or CC-SA-3.0)|END|
src/main/help/help/shared/note.png||Oxygen Icons - LGPL 3.0|||Oxygen icon theme (dual license; LGPL or CC-SA-3.0)|END|
src/main/help/help/shared/note.yellow.png||Oxygen Icons - LGPL 3.0|||Oxygen icon theme (dual license; LGPL or CC-SA-3.0)|END|
src/main/help/help/shared/redo.png||GHIDRA||||END|
src/main/help/help/shared/tip.png||Oxygen Icons - LGPL 3.0|||Oxygen icon theme (dual license; LGPL or CC-SA-3.0)|END|
src/main/help/help/shared/undo.png||GHIDRA||||END|
src/main/help/help/shared/warning.png||Oxygen Icons - LGPL 3.0|||Oxygen icon theme (dual license; LGPL or CC-SA-3.0)|END|
src/main/help/help/topics/PlacheholderTopic/Placeholder.htm||GHIDRA||||END|
src/main/java/docking/dnd/package.html||GHIDRA||reviewed||END|
src/main/java/docking/options/editor/package.html||GHIDRA||reviewed||END|
src/main/java/docking/widgets/fieldpanel/package.html||GHIDRA||reviewed||END|

View file

@ -0,0 +1,54 @@
<?xml version='1.0' encoding='ISO-8859-1' ?>
<!--
This is an XML file intended to be parsed by the Ghidra help system. It is loosely based
upon the JavaHelp table of contents document format. The Ghidra help system uses a
TOC_Source.xml file to allow a module with help to define how its contents appear in the
Ghidra help viewer's table of contents. The main document (in the Base module)
defines a basic structure for the
Ghidra table of contents system. Other TOC_Source.xml files may use this structure to insert
their files directly into this structure (and optionally define a substructure).
In this document, a tag can be either a <tocdef> or a <tocref>. The former is a definition
of an XML item that may have a link and may contain other <tocdef> and <tocref> children.
<tocdef> items may be referred to in other documents by using a <tocref> tag with the
appropriate id attribute value. Using these two tags allows any module to define a place
in the table of contents system (<tocdef>), which also provides a place for
other TOC_Source.xml files to insert content (<tocref>).
During the help build time, all TOC_Source.xml files will be parsed and validated to ensure
that all <tocref> tags point to valid <tocdef> tags. From these files will be generated
<module name>_TOC.xml files, which are table of contents files written in the format
desired by the JavaHelp system. Additionally, the genated files will be merged together
as they are loaded by the JavaHelp system. In the end, when displaying help in the Ghidra
help GUI, there will be on table of contents that has been created from the definitions in
all of the modules' TOC_Source.xml files.
Tags and Attributes
<tocdef>
-id - the name of the definition (this must be unique across all TOC_Source.xml files)
-text - the display text of the node, as seen in the help GUI
-target** - the file to display when the node is clicked in the GUI
-sortgroup - this is a string that defines where a given node should appear under a given
parent. The string values will be sorted by the JavaHelp system using
a javax.text.RulesBasedCollator. If this attribute is not specified, then
the text of attribute will be used.
<tocref>
-id - The id of the <tocdef> that this reference points to
**The URL for the target is relative and should start with 'help/topics'. This text is
used by the Ghidra help system to provide a universal starting point for all links so that
they can be resolved at runtime, across modules.
-->
<tocroot>
</tocroot>

View file

@ -0,0 +1,64 @@
/* ###
* 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.
*/
/*
WARNING!
This file is copied to all help directories. If you change this file, you must copy it
to each src/main/help/help/shared directory.
Java Help Note: JavaHelp does not accept sizes (like in 'margin-top') in anything but
px (pixel) or with no type marking.
*/
body { margin-bottom: 50px; margin-left: 10px; margin-right: 10px; margin-top: 10px; } /* some padding to improve readability */
li { font-family:times new roman; font-size:14pt; }
h1 { color:#000080; font-family:times new roman; font-size:36pt; font-style:italic; font-weight:bold; text-align:center; }
h2 { margin: 10px; margin-top: 20px; color:#984c4c; font-family:times new roman; font-size:18pt; font-weight:bold; }
h3 { margin-left: 10px; margin-top: 20px; color:#0000ff; font-family:times new roman; `font-size:14pt; font-weight:bold; }
h4 { margin-left: 10px; margin-top: 20px; font-family:times new roman; font-size:14pt; font-style:italic; }
/*
P tag code. Most of the help files nest P tags inside of blockquote tags (the was the
way it had been done in the beginning). The net effect is that the text is indented. In
modern HTML we would use CSS to do this. We need to support the Ghidra P tags, nested in
blockquote tags, as well as naked P tags. The following two lines accomplish this. Note
that the 'blockquote p' definition will inherit from the first 'p' definition.
*/
p { margin-left: 40px; font-family:times new roman; font-size:14pt; }
blockquote p { margin-left: 10px; }
p.providedbyplugin { color:#7f7f7f; margin-left: 10px; font-size:14pt; margin-top:100px }
p.ProvidedByPlugin { color:#7f7f7f; margin-left: 10px; font-size:14pt; margin-top:100px }
p.relatedtopic { color:#800080; margin-left: 10px; font-size:14pt; }
p.RelatedTopic { color:#800080; margin-left: 10px; font-size:14pt; }
/*
We wish for a tables to have space between it and the preceding element, so that text
is not too close to the top of the table. Also, nest the table a bit so that it is clear
the table relates to the preceding text.
*/
table { margin-left: 20px; margin-top: 10px; width: 80%;}
td { font-family:times new roman; font-size:14pt; vertical-align: top; }
th { font-family:times new roman; font-size:14pt; font-weight:bold; background-color: #EDF3FE; }
/*
Code-like formatting for things such as file system paths and proper names of classes,
methods, etc. To apply this to a file path, use this syntax:
<CODE CLASS="path">...</CODE>
*/
code { color: black; font-weight: bold; font-family: courier new, monospace; font-size: 14pt; white-space: nowrap; }
code.path { color: #4682B4; font-weight: bold; font-family: courier new, monospace; font-size: 14pt; white-space: nowrap; }

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 859 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -0,0 +1,17 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Frameset//EN">
<HTML>
<HEAD>
<TITLE>Placholder Title</TITLE>
<LINK rel="stylesheet" type="text/css" href="../../shared/Frontpage.css">
</HEAD>
<BODY>
<H1>Stub</H1>
<P>Blah blah</P>
<P class="relatedtopic">Related Topics:</P>
</BODY>
</HTML>

View file

@ -24,11 +24,11 @@ import javax.swing.event.ChangeListener;
import org.jdom.Element;
import docking.help.HelpService;
import docking.widgets.OptionDialog;
import docking.widgets.tabbedpane.DockingTabRenderer;
import ghidra.util.*;
import ghidra.util.exception.AssertException;
import help.HelpService;
import utilities.util.reflection.ReflectionUtilities;
/**

View file

@ -23,10 +23,10 @@ import java.util.*;
import javax.swing.*;
import docking.action.*;
import docking.help.HelpDescriptor;
import docking.help.HelpService;
import ghidra.util.*;
import ghidra.util.exception.AssertException;
import help.HelpDescriptor;
import help.HelpService;
import utilities.util.reflection.ReflectionUtilities;
/**

View file

@ -1,113 +0,0 @@
/* ###
* 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;
import java.awt.*;
import javax.swing.JButton;
import docking.help.HelpDescriptor;
import docking.help.HelpService;
import ghidra.util.HelpLocation;
import ghidra.util.Msg;
public class DefaultHelpService implements HelpService {
@Override
public void showHelp(Object helpObj, boolean infoOnly, Component parent) {
if (infoOnly) {
displayHelpInfo(helpObj);
return;
}
}
@Override
public void showHelp(java.net.URL url) {
// no-op
}
@Override
public void showHelp(HelpLocation location) {
// no-op
}
@Override
public void excludeFromHelp(Object helpObject) {
// no-op
}
@Override
public boolean isExcludedFromHelp(Object helpObject) {
return false;
}
@Override
public void clearHelp(Object helpObject) {
// no-op
}
@Override
public void registerHelp(Object helpObj, HelpLocation helpLocation) {
// no-op
}
@Override
public HelpLocation getHelpLocation(Object object) {
return null;
}
@Override
public boolean helpExists() {
return false;
}
private void displayHelpInfo(Object helpObj) {
String msg = getHelpInfo(helpObj);
Msg.showInfo(this, null, "Help Info", msg);
}
private String getHelpInfo(Object helpObj) {
if (helpObj == null) {
return "Help Object is null";
}
StringBuilder buffy = new StringBuilder();
buffy.append("HELP OBJECT: " + helpObj.getClass().getName());
buffy.append("\n");
if (helpObj instanceof HelpDescriptor) {
HelpDescriptor helpDescriptor = (HelpDescriptor) helpObj;
buffy.append(helpDescriptor.getHelpInfo());
}
else if (helpObj instanceof JButton) {
JButton button = (JButton) helpObj;
buffy.append(" BUTTON: " + button.getText());
buffy.append("\n");
Component c = button;
while (c != null && !(c instanceof Window)) {
c = c.getParent();
}
if (c instanceof Dialog) {
buffy.append(" DIALOG: " + ((Dialog) c).getTitle());
buffy.append("\n");
}
if (c instanceof Frame) {
buffy.append(" FRAME: " + ((Frame) c).getTitle());
buffy.append("\n");
}
}
return buffy.toString();
}
}

View file

@ -29,13 +29,13 @@ import org.jdesktop.animation.timing.TimingTargetAdapter;
import docking.action.*;
import docking.actions.KeyBindingUtils;
import docking.event.mouse.GMouseListenerAdapter;
import docking.help.HelpService;
import docking.menu.DialogToolbarButton;
import docking.util.AnimationUtils;
import docking.widgets.label.GDHtmlLabel;
import ghidra.util.*;
import ghidra.util.exception.AssertException;
import ghidra.util.task.*;
import help.HelpService;
import utility.function.Callback;
/**

View file

@ -23,9 +23,9 @@ import javax.swing.*;
import javax.swing.FocusManager;
import docking.action.DockingActionIf;
import docking.help.HelpService;
import ghidra.util.CascadedDropTarget;
import ghidra.util.HelpLocation;
import help.HelpService;
/**
* Wrapper class for user components. Adds the title, local toolbar and provides the drag target

View file

@ -30,8 +30,6 @@ import org.jdesktop.animation.timing.Animator.RepeatBehavior;
import org.jdesktop.animation.timing.TimingTargetAdapter;
import org.jdesktop.animation.timing.interpolation.PropertySetter;
import docking.help.Help;
import docking.help.HelpService;
import docking.util.AnimationUtils;
import generic.util.WindowUtilities;
import generic.util.image.ImageUtils;
@ -41,6 +39,8 @@ import ghidra.util.HelpLocation;
import ghidra.util.Msg;
import ghidra.util.bean.GGlassPane;
import ghidra.util.bean.GGlassPanePainter;
import help.Help;
import help.HelpService;
import resources.ResourceManager;
/**

View file

@ -25,10 +25,10 @@ import javax.swing.*;
import org.apache.commons.collections4.map.LazyMap;
import docking.framework.ApplicationInformationDisplayFactory;
import docking.help.HelpDescriptor;
import generic.util.WindowUtilities;
import ghidra.framework.Application;
import ghidra.util.bean.GGlassPane;
import help.HelpDescriptor;
// NOTE: this class has a static focus component variable that is set whenever the dialog gets
// activated and is scheduled to get focus at a later time. This variable is static so that only

View file

@ -31,7 +31,6 @@ import org.jdom.Element;
import docking.action.DockingActionIf;
import docking.actions.*;
import docking.help.HelpService;
import docking.widgets.PasswordDialog;
import generic.util.WindowUtilities;
import ghidra.framework.OperatingSystem;
@ -41,6 +40,8 @@ import ghidra.util.*;
import ghidra.util.datastruct.*;
import ghidra.util.exception.AssertException;
import ghidra.util.task.SwingUpdateManager;
import help.Help;
import help.HelpService;
import util.CollectionUtils;
/**
@ -72,11 +73,6 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
public static final String DOCKING_WINDOWS_OWNER = "DockingWindows";
public static final String TOOL_PREFERENCES_XML_NAME = "PREFERENCES";
/**
* The helpService field should be set to the appropriate help service provider.
*/
private static HelpService helpService = new DefaultHelpService();
// we use a list to maintain order
private static List<DockingWindowManager> instances = new ArrayList<>();
@ -161,25 +157,13 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
return "DockingWindowManager: " + root.getTitle();
}
/**
* Sets the help service for the all docking window managers.
*
* @param helpSvc the help service to use.
*/
public static void setHelpService(HelpService helpSvc) {
if (helpSvc == null) {
throw new IllegalArgumentException("HelpService may not be null");
}
helpService = helpSvc;
}
/**
* Returns the global help service.
*
* @return the global help service.
*/
public static HelpService getHelpService() {
return helpService;
return Help.getHelpService();
}
private static synchronized void addInstance(DockingWindowManager winMgr) {
@ -516,6 +500,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
}
private void registerHelpLocation(ComponentProvider provider, HelpLocation helpLocation) {
HelpService helpService = Help.getHelpService();
HelpLocation registeredHelpLocation = helpService.getHelpLocation(provider);
if (registeredHelpLocation != null) {
return; // nothing to do; location already registered

View file

@ -21,7 +21,7 @@ import java.util.Set;
import javax.swing.*;
import docking.ActionContext;
import docking.help.HelpDescriptor;
import help.HelpDescriptor;
/**
* The base interface for clients that wish to create commands to be registered with a tool.

View file

@ -20,9 +20,9 @@ import java.awt.*;
import javax.swing.KeyStroke;
import docking.*;
import docking.help.HelpService;
import ghidra.util.HelpLocation;
import ghidra.util.SystemUtilities;
import help.HelpService;
public class HelpAction extends DockingAction {

View file

@ -1,237 +0,0 @@
/* ###
* 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.help;
import java.awt.Component;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.beans.PropertyChangeListener;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Hashtable;
import java.util.Locale;
import javax.help.*;
import javax.help.Map.ID;
import javax.help.event.HelpModelEvent;
import javax.help.plaf.HelpNavigatorUI;
import javax.help.plaf.basic.BasicFavoritesCellRenderer;
import javax.help.plaf.basic.BasicFavoritesNavigatorUI;
import javax.swing.JComponent;
import javax.swing.JTree;
import javax.swing.tree.DefaultMutableTreeNode;
import ghidra.util.Msg;
/**
* This class allows us to change the renderer of the favorites tree.
*/
public class CustomFavoritesView extends FavoritesView {
public CustomFavoritesView(HelpSet hs, String name, String label,
@SuppressWarnings("rawtypes") Hashtable params) {
this(hs, name, label, hs.getLocale(), params);
}
public CustomFavoritesView(HelpSet hs, String name, String label, Locale locale,
@SuppressWarnings("rawtypes") Hashtable params) {
super(hs, name, label, locale, params);
}
@Override
public Component createNavigator(HelpModel model) {
return new CustomHelpFavoritesNavigator(this, model);
}
//==================================================================================================
// Inner Classes
//==================================================================================================
class CustomHelpFavoritesNavigator extends JHelpFavoritesNavigator {
CustomHelpFavoritesNavigator(NavigatorView view, HelpModel model) {
super(view, model);
}
@Override
public void setUI(HelpNavigatorUI ui) {
super.setUI(new CustomFavoritesNavigatorUI(this));
}
}
class CustomFavoritesNavigatorUI extends BasicFavoritesNavigatorUI {
private PropertyChangeListener titleListener;
CustomFavoritesNavigatorUI(JHelpFavoritesNavigator b) {
super(b);
}
@Override
public void installUI(JComponent c) {
super.installUI(c);
tree.addKeyListener(new KeyAdapter() {
@Override
public void keyReleased(java.awt.event.KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_DELETE ||
e.getKeyCode() == KeyEvent.VK_BACK_SPACE) {
removeAction.actionPerformed(null);
}
}
});
// Note: add a listener to fix the bug described in 'idChanged()' below
HelpModel model = favorites.getModel();
titleListener = e -> {
if (lastIdEvent == null) {
return;
}
currentTitle = (String) e.getNewValue();
if (currentTitle == null) {
return;
}
String lastTitle = lastIdEvent.getHistoryName();
if (!currentTitle.equals(lastTitle)) {
resendNewEventWithFixedTitle(lastIdEvent, currentTitle);
}
};
model.addPropertyChangeListener(titleListener);
}
@Override
public void uninstallUI(JComponent c) {
HelpModel model = favorites.getModel();
if (model != null) {
model.removePropertyChangeListener(titleListener);
}
super.uninstallUI(c);
}
private void resendNewEventWithFixedTitle(HelpModelEvent originalEvent, String title) {
HelpModelEvent e = originalEvent;
HelpModelEvent newEvent =
new HelpModelEvent(e.getSource(), e.getID(), e.getURL(), title, favorites);
idChanged(newEvent);
}
private HelpModelEvent lastIdEvent;
private String currentTitle;
@Override
protected void setCellRenderer(NavigatorView view, JTree tree) {
tree.setCellRenderer(new CustomFavoritesCellRenderer(favorites.getModel()));
}
@Override
public void idChanged(HelpModelEvent e) {
//
// Overridden to track the change events. We need this to fix a bug where our
// parent class will get the wrong title of the page being loaded *when the user
// has navigated via a hyperlink*. The result of using the wrong title
// manifests itself when the user makes a 'favorite' item--the title will not
// match the 'favorite'd page.
//
// this is how the super class stores off it's 'contentTitle' variable
lastIdEvent = e;
super.idChanged(e);
}
}
class CustomFavoritesCellRenderer extends BasicFavoritesCellRenderer {
private final HelpModel helpModel;
public CustomFavoritesCellRenderer(HelpModel helpModel) {
this.helpModel = helpModel;
}
@Override
public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel,
boolean expanded, boolean leaf, int row, boolean isFocused) {
CustomFavoritesCellRenderer renderer =
(CustomFavoritesCellRenderer) super.getTreeCellRendererComponent(tree, value, sel,
expanded, leaf, row, isFocused);
Object o = ((DefaultMutableTreeNode) value).getUserObject();
FavoritesItem item = (FavoritesItem) o;
if (item == null) {
return renderer;
}
HelpSet helpSet = helpModel.getHelpSet();
Map combinedMap = helpSet.getCombinedMap();
URL URL = getURL(item, helpSet, combinedMap);
if (URL == null) {
// should only happen if the user has old favorites; trust the old name
return renderer;
}
String text = URL.getFile();
int index = text.lastIndexOf('/');
if (index != -1) {
// we want just the filename
text = text.substring(index + 1);
}
String ref = URL.getRef();
if (ref != null) {
text += "#" + ref;
}
renderer.setText(item.getName() + " - " + text);
return renderer;
}
private URL getURL(FavoritesItem item, HelpSet helpSet, Map combinedMap) {
String target = item.getTarget();
if (target == null) {
// use the URL of the item
return item.getURL();
}
ID newID = null;
try {
newID = ID.create(target, helpSet);
}
catch (BadIDException e) {
Msg.debug(this, "Invalid help ID; Mabye bad favorite bookmark?: " + target);
return null;
}
try {
return combinedMap.getURLFromID(newID);
}
catch (MalformedURLException e) {
//shouldn't happen
Msg.error(this, "Unexpected Exception", e);
}
return null;
}
}
}

View file

@ -1,97 +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.help;
import ghidra.util.Msg;
import java.awt.*;
import java.util.*;
import javax.help.*;
import javax.help.plaf.HelpNavigatorUI;
import javax.help.plaf.basic.BasicSearchNavigatorUI;
import javax.help.search.SearchEvent;
public class CustomSearchView extends SearchView {
public CustomSearchView(HelpSet hs, String name, String label, Locale locale,
@SuppressWarnings("rawtypes") Hashtable params) {
super(hs, name, label, locale, params);
}
@Override
public Component createNavigator(HelpModel model) {
return new CustomHelpSearchNavigator(this, model);
}
//==================================================================================================
// Inner Classes
//==================================================================================================
class CustomHelpSearchNavigator extends JHelpSearchNavigator {
public CustomHelpSearchNavigator(NavigatorView view, HelpModel model) {
super(view, model);
}
@Override
public void setUI(HelpNavigatorUI ui) {
super.setUI(new CustomSearchNavigatorUI(this));
}
}
class CustomSearchNavigatorUI extends BasicSearchNavigatorUI {
private boolean hasResults;
public CustomSearchNavigatorUI(JHelpSearchNavigator navigator) {
super(navigator);
}
@Override
public synchronized void searchStarted(SearchEvent e) {
hasResults = false;
super.searchStarted(e);
}
@Override
public synchronized void itemsFound(SearchEvent e) {
super.itemsFound(e);
@SuppressWarnings("rawtypes")
Enumeration searchItems = e.getSearchItems();
if (searchItems == null) {
return;
}
hasResults |= e.getSearchItems().hasMoreElements();
}
@Override
public synchronized void searchFinished(SearchEvent e) {
super.searchFinished(e);
if (!hasResults) {
KeyboardFocusManager kfm = KeyboardFocusManager.getCurrentKeyboardFocusManager();
Window activeWindow = kfm.getActiveWindow();
Msg.showInfo(this, activeWindow, "No Results Founds",
"No search results found for \"" + e.getParams() + "\"");
}
}
}
}

View file

@ -1,524 +0,0 @@
/* ###
* 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.help;
import java.awt.Component;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Hashtable;
import java.util.Locale;
import javax.help.*;
import javax.help.Map.ID;
import javax.help.event.HelpModelEvent;
import javax.help.plaf.HelpNavigatorUI;
import javax.help.plaf.basic.BasicTOCCellRenderer;
import javax.help.plaf.basic.BasicTOCNavigatorUI;
import javax.swing.*;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreePath;
import ghidra.util.Msg;
import ghidra.util.SystemUtilities;
/**
* A custom Table of Contents view that we specify in our JavaHelp xml documents. This view
* lets us install custom renderers and custom tree items for use by those renderers. These
* renderers let us display custom text defined by the TOC_Source.xml files. We also add some
* utility like: tooltips in development mode, node selection when pressing F1.
*/
public class CustomTOCView extends TOCView {
private CustomTOCNavigatorUI ui;
private boolean isSelectingNodeInternally;
// Hashtable
public CustomTOCView(HelpSet hs, String name, String label,
@SuppressWarnings("rawtypes") Hashtable params) {
this(hs, name, label, hs.getLocale(), params);
}
// Hashtable
public CustomTOCView(HelpSet hs, String name, String label, Locale locale,
@SuppressWarnings("rawtypes") Hashtable params) {
super(hs, name, label, locale, params);
}
@Override
// overrode this method to install our custom UI, which lets us use our custom renderer
public Component createNavigator(HelpModel model) {
JHelpTOCNavigator helpTOCNavigator = new JHelpTOCNavigator(this, model) {
@Override
public void setUI(HelpNavigatorUI newUI) {
CustomTOCView.this.ui = new CustomTOCNavigatorUI(this);
super.setUI(CustomTOCView.this.ui);
}
};
return helpTOCNavigator;
}
public HelpModel getHelpModel() {
return ui.getHelpModel();
}
@Override
// overrode this method to install our custom factory
public DefaultMutableTreeNode getDataAsTree() {
DefaultMutableTreeNode superNode = super.getDataAsTree();
if (superNode.getChildCount() == 0) {
return superNode; // something is not initialized
}
@SuppressWarnings("rawtypes")
Hashtable viewParameters = getParameters();
String TOCData = (String) viewParameters.get("data");
HelpSet helpSet = getHelpSet();
URL helpSetURL = helpSet.getHelpSetURL();
URL url;
try {
url = new URL(helpSetURL, TOCData);
}
catch (Exception ex) {
throw new Error("Unable to create tree for view data: " + ex);
}
return parse(url, helpSet, helpSet.getLocale(), new CustomDefaultTOCFactory(), this);
}
/**
* Our custom factory that knows how to look for extra XML attributes and how to
* create our custom tree items
*/
public static class CustomDefaultTOCFactory extends DefaultTOCFactory {
@Override
public TreeItem createItem(String tagName, @SuppressWarnings("rawtypes") Hashtable atts,
HelpSet hs, Locale locale) {
try {
return doCreateItem(tagName, atts, hs, locale);
}
catch (Exception e) {
Msg.error(this, "Unexected error creating a TOC item", e);
throw new RuntimeException("Unexpected error creating a TOC item", e);
}
}
private TreeItem doCreateItem(String tagName, @SuppressWarnings("rawtypes") Hashtable atts,
HelpSet hs, Locale locale) {
TreeItem item = super.createItem(tagName, atts, hs, locale);
CustomTreeItemDecorator newItem = new CustomTreeItemDecorator((TOCItem) item);
if (atts != null) {
String displayText = (String) atts.get("display");
newItem.setDisplayText(displayText);
String tocID = (String) atts.get("toc_id");
newItem.setTocID(tocID);
}
return newItem;
}
}
/**
* Our hook to install our custom cell renderer.
*/
class CustomTOCNavigatorUI extends BasicTOCNavigatorUI {
public CustomTOCNavigatorUI(JHelpTOCNavigator b) {
super(b);
}
@Override
public void installUI(JComponent c) {
super.installUI(c);
tree.setExpandsSelectedPaths(true);
}
@Override
protected void setCellRenderer(NavigatorView view, JTree tree) {
Map map = view.getHelpSet().getCombinedMap();
tree.setCellRenderer(new CustomCellRenderer(map, (TOCView) view));
ToolTipManager.sharedInstance().registerComponent(tree);
}
public HelpModel getHelpModel() {
JHelpNavigator helpNavigator = getHelpNavigator();
return helpNavigator.getModel();
}
// Overridden to change the value used for the 'historyName', which we want to be our
// display name and not the item's name
@Override
public void valueChanged(TreeSelectionEvent e) {
if (isSelectingNodeInternally) {
// ignore our own selection events, as this method will get called twice if we don't
return;
}
JHelpNavigator navigator = getHelpNavigator();
HelpModel helpModel = navigator.getModel();
TreeItem treeItem = getSelectedItem(e, navigator);
if (treeItem == null) {
return; // nothing selected
}
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;
String customDisplayText = customItem.getDisplayText();
try {
helpModel.setCurrentID(itemID, customDisplayText, navigator);
}
catch (InvalidHelpSetContextException ex) {
Msg.error(this, "Exception setting new help item ID", ex);
}
}
private TOCItem getSelectedItem(TreeSelectionEvent e, JHelpNavigator navigator) {
TreePath newLeadSelectionPath = e.getNewLeadSelectionPath();
if (newLeadSelectionPath == null) {
navigator.setSelectedItems(null);
return null;
}
DefaultMutableTreeNode node =
(DefaultMutableTreeNode) newLeadSelectionPath.getLastPathComponent();
TOCItem treeItem = (TOCItem) node.getUserObject();
navigator.setSelectedItems(new TreeItem[] { treeItem });
return treeItem;
}
// Overridden to try to find a parent file for IDs that are based upon anchors within
// a file
@Override
public synchronized void idChanged(HelpModelEvent e) {
selectNodeForID(e.getURL(), e.getID());
}
private void selectNodeForID(URL url, ID ID) {
if (ID == null) {
ID = getClosestID(url);
}
TreePath path = tree.getSelectionPath();
if (isAlreadySelected(path, ID)) {
return;
}
DefaultMutableTreeNode node = getNodeForID(topNode, ID);
if (node != null) {
isSelectingNodeInternally = true;
TreePath newPath = new TreePath(node.getPath());
tree.setSelectionPath(newPath);
tree.scrollPathToVisible(newPath);
isSelectingNodeInternally = false;
return;
}
// See if the given ID is based upon a URL with an anchor. If that is the case, then
// there may be a node for the parent file of that URL. In that case, select the
// parent file.
if (url == null) {
clearSelection();
return;
}
String urlString = url.toExternalForm();
int anchorIndex = urlString.indexOf('#');
if (anchorIndex < 0) {
clearSelection();
return;
}
urlString = urlString.substring(0, anchorIndex);
try {
URL newURL = new URL(urlString);
selectNodeForID(newURL, null);
}
catch (MalformedURLException e) {
// shouldn't happen, as we are starting with a valid URL
Msg.debug(this,
"Unexpected error create a help URL from an existing URL: " + urlString, e);
}
}
private ID getClosestID(URL url) {
HelpModel helpModel = toc.getModel();
HelpSet helpSet = helpModel.getHelpSet();
Map combinedMap = helpSet.getCombinedMap();
return combinedMap.getClosestID(url);
}
private boolean isAlreadySelected(TreePath path, ID id) {
if (path == null) {
return false;
}
Object pathComponent = path.getLastPathComponent();
if (!(pathComponent instanceof DefaultMutableTreeNode)) {
return false;
}
DefaultMutableTreeNode treeNode = (DefaultMutableTreeNode) pathComponent;
TOCItem item = (TOCItem) treeNode.getUserObject();
if (item == null) {
return false;
}
ID selectedID = item.getID();
return selectedID != null && selectedID.equals(id);
}
private DefaultMutableTreeNode getNodeForID(DefaultMutableTreeNode node, ID ID) {
if (ID == null) {
return null;
}
if (isNodeID(node, ID)) {
return node;
}
int childCount = node.getChildCount();
for (int i = 0; i < childCount; i++) {
DefaultMutableTreeNode matchingNode =
getNodeForID((DefaultMutableTreeNode) node.getChildAt(i), ID);
if (matchingNode != null) {
return matchingNode;
}
}
return null;
}
private boolean isNodeID(DefaultMutableTreeNode node, ID ID) {
Object userObject = node.getUserObject();
if (!(userObject instanceof TOCItem)) {
return false;
}
TOCItem item = (TOCItem) userObject;
ID nodeID = item.getID();
if (nodeID == null) {
return false;
}
return nodeID.equals(ID);
}
private void clearSelection() {
isSelectingNodeInternally = true;
tree.clearSelection();
isSelectingNodeInternally = false;
}
}
static class CustomCellRenderer extends BasicTOCCellRenderer {
public CustomCellRenderer(Map map, TOCView view) {
super(map, view);
}
@Override
public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel,
boolean expanded, boolean leaf, int row, boolean isFocused) {
CustomCellRenderer renderer =
(CustomCellRenderer) super.getTreeCellRendererComponent(tree, value, sel, expanded,
leaf, row, isFocused);
TOCItem item = (TOCItem) ((DefaultMutableTreeNode) value).getUserObject();
if (item == null) {
return renderer;
}
CustomTreeItemDecorator customItem = (CustomTreeItemDecorator) item;
renderer.setText(customItem.getDisplayText());
if (!SystemUtilities.isInDevelopmentMode()) {
return renderer;
}
URL url = customItem.getURL();
if (url != null) {
renderer.setToolTipText(url.toExternalForm());
return renderer;
}
ID id = customItem.getID();
if (id != null) {
renderer.setToolTipText("Missing Help - " + id.id + " in '" + id.hs + "' help set");
return renderer;
}
// this can happen if there is no 'target' attribute in the TOC
// (see TOCView.createItem())
return renderer;
}
}
/**
* A custom tree item that allows us to store and retrieve custom attributes that we parsed
* from the TOC xml document.
*/
public static class CustomTreeItemDecorator extends javax.help.TOCItem {
private final TOCItem wrappedItem;
private String displayText;
private String tocID;
private URL cachedURL;
public CustomTreeItemDecorator(javax.help.TOCItem wrappedItem) {
super(wrappedItem.getID(), wrappedItem.getImageID(), wrappedItem.getHelpSet(),
wrappedItem.getLocale());
this.wrappedItem = wrappedItem;
}
void setDisplayText(String text) {
this.displayText = text;
}
public String getDisplayText() {
return displayText;
}
void setTocID(String tocID) {
this.tocID = tocID;
}
public String getTocID() {
return tocID;
}
@Override
public boolean equals(Object obj) {
return wrappedItem.equals(obj);
}
@Override
public int getExpansionType() {
return wrappedItem.getExpansionType();
}
@Override
public HelpSet getHelpSet() {
return wrappedItem.getHelpSet();
}
@Override
public ID getID() {
return wrappedItem.getID();
}
@Override
public ID getImageID() {
return wrappedItem.getImageID();
}
@Override
public Locale getLocale() {
return wrappedItem.getLocale();
}
@Override
public String getMergeType() {
return wrappedItem.getMergeType();
}
@Override
public String getName() {
return wrappedItem.getName();
}
@Override
public String getPresentation() {
return wrappedItem.getPresentation();
}
@Override
public String getPresentationName() {
return wrappedItem.getPresentationName();
}
@Override
public URL getURL() {
if (cachedURL == null) {
cachedURL = wrappedItem.getURL();
}
return cachedURL;
}
@Override
public int hashCode() {
return wrappedItem.hashCode();
}
@Override
public void setExpansionType(int type) {
wrappedItem.setExpansionType(type);
}
@Override
public void setHelpSet(HelpSet hs) {
wrappedItem.setHelpSet(hs);
}
@Override
public void setID(ID id) {
wrappedItem.setID(id);
}
@Override
public void setMergeType(String mergeType) {
wrappedItem.setMergeType(mergeType);
}
@Override
public void setName(String name) {
wrappedItem.setName(name);
}
@Override
public void setPresentation(String presentation) {
wrappedItem.setPresentation(presentation);
}
@Override
public void setPresentationName(String presentationName) {
wrappedItem.setPresentationName(presentationName);
}
@Override
public String toString() {
return displayText;
}
}
}

View file

@ -19,7 +19,6 @@ import java.awt.*;
import java.awt.geom.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.IOException;
import java.net.URL;
import java.util.List;
@ -27,7 +26,8 @@ import javax.help.*;
import javax.help.event.HelpModelEvent;
import javax.help.event.HelpModelListener;
import javax.swing.*;
import javax.swing.text.*;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.html.HTML;
import javax.swing.text.html.HTMLDocument;
@ -39,108 +39,45 @@ import docking.util.AnimationPainter;
import docking.util.AnimationUtils;
import ghidra.framework.preferences.Preferences;
import ghidra.util.Msg;
import ghidra.util.Swing;
import ghidra.util.bean.GGlassPane;
import resources.ResourceManager;
// NOTE: for JH 2.0, this class has been rewritten to not
// access the 'frame' and 'dialog' variable directly
import help.CustomTOCView;
import help.GHelpBroker;
/**
* Ghidra help broker that displays the help set; sets the Ghidra icon on
* the help frame and attempts to maintain the user window size.
* An extension of the {@link GHelpBroker} that allows {@code Docking} classes to be installed.
* <p>
* Additions include a search feature a navigation aid.
*/
public class GHelpBroker extends DefaultHelpBroker {
public class DockingHelpBroker extends GHelpBroker {
private static final List<Image> ICONS = ApplicationInformationDisplayFactory.getWindowIcons();
private static final int MAX_CALLOUT_RETRIES = 3;
private Dimension windowSize = new Dimension(1100, 700);
private JEditorPane htmlEditorPane;
private Animator lastAnimator;
private URL loadingURL;
private PropertyChangeListener pageLoadListener = new PageLoadingListener();
private HelpModelListener helpModelListener = new HelpIDChangedListener();
private Window activationWindow;
private Animator lastAnimator;
private URL loadingURL;
// Create the zoom in/out icons that will be added to the default jHelp toolbar.
private static final ImageIcon ZOOM_OUT_ICON =
ResourceManager.loadImage("images/list-remove.png");
private static final ImageIcon ZOOM_IN_ICON = ResourceManager.loadImage("images/list-add.png");
/**
* Construct a new GhidraHelpBroker.
* @param hs java help set associated with this help broker
*/
public GHelpBroker(HelpSet hs) {
public DockingHelpBroker(HelpSet hs) {
super(hs);
}
@Override
// Overridden so that we can call the preferred version of setCurrentURL on the HelpModel,
// which fixes a bug with the history list (SCR 7639)
public void setCurrentURL(final URL URL) {
HelpModel model = getHelpModel();
if (model != null) {
model.setCurrentURL(URL, getHistoryName(URL), null);
}
else {
super.setCurrentURL(URL);
}
protected List<Image> getApplicationIcons() {
return ICONS;
}
/* Perform some shenanigans to force Java Help to reload the given URL */
void reloadHelpPage(URL url) {
clearContentViewer();
prepareToCallout(url);
try {
htmlEditorPane.setPage(url);
}
catch (IOException e) {
Msg.error(this, "Unexpected error loading help page: " + url, e);
}
}
private void clearContentViewer() {
htmlEditorPane.getDocument().putProperty(Document.StreamDescriptionProperty, null);
}
private JScrollPane getScrollPane(JEditorPane editorPane) {
Container parent = editorPane.getParent();
while (parent != null) {
if (parent instanceof JScrollPane) {
return (JScrollPane) parent;
}
parent = parent.getParent();
}
return null;
}
private JEditorPane getHTMLEditorPane(JHelpContentViewer contentViewer) {
//
// Intimate Knowledge - construction of the viewer:
//
// -BorderLayout
// -JScrollPane
// -Viewport
// -JHEditorPane extends JEditorPane
//
//
Component[] components = contentViewer.getComponents();
JScrollPane scrollPane = (JScrollPane) components[0];
JViewport viewport = scrollPane.getViewport();
return (JEditorPane) viewport.getView();
}
private HelpModel getHelpModel() {
@Override
protected HelpModel getCustomHelpModel() {
//
// Unusual Code Alert!: We have opened up access to the help system's HelpModel by way
// of our CustomTOCView object that we install elsewhere. We need
// access to the model because of a bug in the help system
// of our CustomTOCView object that we install elsewhere. We need
// access to the model because of a bug in the help system
// (SCR 7639). Unfortunately, the Java Help system does not give us
// access to the model directly, but we have opened up the access from
// one of our overriding components. The following code is
// one of our overriding components. The following code is
// digging-out our custom component to get at the model. An
// alternative approach would be to just use reflection and violate
// security restrictions, but that seemed worse than this solution.
@ -160,133 +97,18 @@ public class GHelpBroker extends DefaultHelpBroker {
}
@Override
public void setDisplayed(boolean b) {
if (!b) {
super.setDisplayed(b);
return;
}
// this must be before any call that triggers the help system to create its window
initializeScreenDevice();
WindowPresentation windowPresentation = getWindowPresentation();
updateWindowSize(windowPresentation);
// this has to be before getHelpWindow() or the value returned will be null
super.setDisplayed(b);
initializeUIWindowPresentation(windowPresentation);
}
private void initializeScreenDevice() {
if (isInitialized()) {
return;
}
if (activationWindow == null) {
// This can happen when we show the 'What's New' help page on a fresh install. In
// that case, we were not activated from an existing window, thus, there may
// be no parent window.
return;
}
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice[] gs = ge.getScreenDevices();
GraphicsConfiguration config = activationWindow.getGraphicsConfiguration();
GraphicsDevice parentDevice = config.getDevice();
for (int i = 0; i < gs.length; i++) {
if (gs[i] == parentDevice) {
// update the help window's screen to match that of the parent
setScreen(i);
}
}
}
private void initializeUIWindowPresentation(WindowPresentation windowPresentation) {
Window helpWindow = windowPresentation.getHelpWindow();
Container contentPane = null;
if (helpWindow instanceof JFrame) {
JFrame frame = (JFrame) helpWindow;
installRootPane(frame);
frame.setIconImages(ICONS);
contentPane = frame.getContentPane();
}
else if (helpWindow instanceof JDialog) {
JDialog dialog = (JDialog) helpWindow;
installRootPane(dialog);
contentPane = dialog.getContentPane();
}
initializeUIComponents(contentPane);
}
private boolean isInitialized() {
return htmlEditorPane != null;
}
private void initializeUIComponents(Container contentPane) {
if (isInitialized()) {
return;// already initialized
}
Component[] components = contentPane.getComponents();
JHelp jHelp = (JHelp) components[0];
addCustomToolbarItems(jHelp);
JHelpContentViewer contentViewer = jHelp.getContentViewer();
htmlEditorPane = getHTMLEditorPane(contentViewer);
// just creating the search wires everything together
HelpModel helpModel = getHelpModel();
protected void installHelpSearcher(JHelp jHelp, HelpModel helpModel) {
helpModel.addHelpModelListener(helpModelListener);
new HelpViewSearcher(jHelp, helpModel);
installActions(jHelp);
}
/**
* Create zoom in/out buttons on the default help window toolbar.
* @param jHelp the java help object used to retrieve the help components
*/
protected void addCustomToolbarItems(final JHelp jHelp) {
for (Component component : jHelp.getComponents()) {
if (component instanceof JToolBar) {
JToolBar toolbar = (JToolBar) component;
toolbar.addSeparator();
ImageIcon zoomOutIcon = ResourceManager.getScaledIcon(ZOOM_OUT_ICON, 24, 24);
JButton zoomOutBtn = new JButton(zoomOutIcon);
zoomOutBtn.setToolTipText("Zoom out");
zoomOutBtn.addActionListener(e -> {
GHelpHTMLEditorKit.zoomOut();
// Need to reload the page to force the scroll panes to resize properly. A
// simple revalidate/repaint won't do it.
reloadHelpPage(getCurrentURL());
});
toolbar.add(zoomOutBtn);
ImageIcon zoomInIcon = ResourceManager.getScaledIcon(ZOOM_IN_ICON, 24, 24);
JButton zoomInBtn = new JButton(zoomInIcon);
zoomInBtn.setToolTipText("Zoom in");
zoomInBtn.addActionListener(e -> {
GHelpHTMLEditorKit.zoomIn();
// Need to reload the page to force the scroll panes to resize properly. A
// simple revalidate/repaint won't do it.
reloadHelpPage(getCurrentURL());
});
toolbar.add(zoomInBtn);
// Once we've found the toolbar we can break out of the loop and stop looking for it.
break;
}
}
@Override
protected void showNavigationAid(URL url) {
prepareToCallout(url);
}
private void installActions(JHelp help) {
@Override
protected void installActions(JHelp help) {
JToolBar toolbar = null;
Component[] components = help.getComponents();
for (Component c : components) {
@ -308,79 +130,24 @@ public class GHelpBroker extends DefaultHelpBroker {
toolbar.add(new JButton(action));
}
private String getHistoryName(URL URL) {
String text = URL.getFile();
int index = text.lastIndexOf('/');
if (index != -1) {
// we want just the filename
text = text.substring(index + 1);
}
String ref = URL.getRef();
if (ref != null) {
text += " - " + ref;
}
return text;
@Override // opened access
protected void reloadHelpPage(URL url) {
super.reloadHelpPage(url);
}
private void installRootPane(JFrame frame) {
Component oldGlassPane = frame.getGlassPane();
if (!(oldGlassPane instanceof GGlassPane)) {
GGlassPane gGlassPane = new GGlassPane();
frame.setGlassPane(gGlassPane);
gGlassPane.setVisible(true);
}
}
//=================================================================================================
// Navigation Aid Section
//=================================================================================================
private void installRootPane(JDialog dialog) {
Component oldGlassPane = dialog.getGlassPane();
if (!(oldGlassPane instanceof GGlassPane)) {
GGlassPane gGlassPane = new GGlassPane();
dialog.setGlassPane(gGlassPane);
gGlassPane.setVisible(true);
}
}
private void updateWindowSize(WindowPresentation presentation) {
if (windowSize == null) {
return;
}
presentation.createHelpWindow();
presentation.setSize(windowSize);
}
@Override
public void setActivationWindow(Window window) {
WindowPresentation windowPresentation = getWindowPresentation();
Window helpWindow = windowPresentation.getHelpWindow();
if (helpWindow == null) {
activationWindow = window;
super.setActivationWindow(window);
return;
}
windowSize = helpWindow.getSize();// remember the previous size
boolean wasModal = isModalWindow(helpWindow);
boolean willBeModal = isModalWindow(window);
if (!wasModal && willBeModal) {
// in this condition, a new window will be shown, but the old one is not properly
// closed by JavaHelp
helpWindow.setVisible(false);
}
super.setActivationWindow(window);
}
private boolean isModalWindow(Window window) {
if (window instanceof Dialog) {
Dialog dialog = (Dialog) window;
if (dialog.isModal()) {
return true;
private JScrollPane getScrollPane(JEditorPane editorPane) {
Container parent = editorPane.getParent();
while (parent != null) {
if (parent instanceof JScrollPane) {
return (JScrollPane) parent;
}
parent = parent.getParent();
}
return false;
return null;
}
private void showNavigationAid() {
@ -398,7 +165,7 @@ public class GHelpBroker extends DefaultHelpBroker {
}
private void calloutReferenceLater() {
SwingUtilities.invokeLater(() -> calloutReference(loadingURL));
Swing.runLater(() -> calloutReference(loadingURL));
}
private void calloutReference(final URL url) {
@ -442,10 +209,10 @@ public class GHelpBroker extends DefaultHelpBroker {
* help pages and when the UI is adjusted in response to those changes.
* <p>
* Note: this method will call itself if the view is not yet updated for the requested
* model change. In that case, this method will call itself again later. This may
* need to happen more than once. However, we will only try a few times and
* model change. In that case, this method will call itself again later. This may
* need to happen more than once. However, we will only try a few times and
* then just give up.
*
*
* @param area the area to call out
* @param callCount the number number of times this method has already been called
*/
@ -477,20 +244,20 @@ public class GHelpBroker extends DefaultHelpBroker {
//
// Unusual Code: Not yet rendered! Try again.
//
SwingUtilities.invokeLater(() -> doCalloutReference(area, numberOfCalls));
Swing.runLater(() -> doCalloutReference(area, numberOfCalls));
return;
}
//
// The area of the HTML content is absolute inside of the entire document.
// However, the user is viewing the document inside of a scroll pane. So, we
// The area of the HTML content is absolute inside of the entire document.
// However, the user is viewing the document inside of a scroll pane. So, we
// want the offset of the element within the viewer, not the absolute position.
//
area.y -= viewPosition.y;
//
// Update the coordinates to be relative to the content pane, which is where we
// Update the coordinates to be relative to the content pane, which is where we
// are doing the painting.
//
Rectangle relativeArea = SwingUtilities.convertRectangle(scrollPane, area, contentPane);
@ -519,7 +286,6 @@ public class GHelpBroker extends DefaultHelpBroker {
loadingURL = url;
// TODO
// updateTitle();
if (isCurrentPage(loadingURL)) {
@ -650,8 +416,8 @@ public class GHelpBroker extends DefaultHelpBroker {
// At 0%, with 100% opacity; at the end, paint with 0% opacity
//
Composite originalComposite = g2d.getComposite();
AlphaComposite alphaComposite = AlphaComposite.getInstance(
AlphaComposite.SrcOver.getRule(), (float) (1 - percentComplete));
AlphaComposite alphaComposite = AlphaComposite
.getInstance(AlphaComposite.SrcOver.getRule(), (float) (1 - percentComplete));
g2d.setComposite(alphaComposite);
double transition = 1 - percentComplete;
@ -686,7 +452,7 @@ public class GHelpBroker extends DefaultHelpBroker {
Shape box = scaler.createTransformedShape(b);
g2d.setColor(Color.GREEN);
g2d.fill(box);
box = transform.createTransformedShape(box);
g2d.setColor(Color.YELLOW);
g2d.fill(box);

View file

@ -1,6 +1,5 @@
/* ###
* 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.
@ -16,16 +15,24 @@
*/
package docking.help;
public interface HelpDescriptor {
/**
* Returns the object for which help locations are defined. This may be the implementor of
* this interface or some other delegate object.
*/
public Object getHelpObject();
/**
* Returns a descriptive String about the help object that this descriptor represents.
*/
public String getHelpInfo();
import java.net.URL;
import javax.help.HelpBroker;
import javax.help.HelpSetException;
import help.GHelpSet;
/**
* An extension of the {@link GHelpSet} that allows {@code Docking} classes to be installed.
*/
public class DockingHelpSet extends GHelpSet {
public DockingHelpSet(ClassLoader loader, URL helpset) throws HelpSetException {
super(loader, helpset);
}
@Override
public HelpBroker createHelpBroker() {
return new DockingHelpBroker(this);
}
}

View file

@ -1,538 +0,0 @@
/* ###
* 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.help;
import java.awt.Desktop;
import java.awt.Image;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.*;
import java.net.*;
import java.util.Collection;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.ImageIcon;
import javax.swing.JEditorPane;
import javax.swing.event.HyperlinkEvent;
import javax.swing.event.HyperlinkListener;
import javax.swing.text.*;
import javax.swing.text.html.*;
import javax.swing.text.html.HTML.Tag;
import generic.jar.ResourceFile;
import ghidra.framework.Application;
import ghidra.framework.preferences.Preferences;
import ghidra.util.Msg;
import resources.*;
import utilities.util.FileUtilities;
/**
* A class that allows Ghidra to intercept JavaHelp navigation events in order to resolve them
* to Ghidra's help system. Without this class, contribution plugins have no way of
* referencing help documents within Ghidra's default help location.
* <p>
* This class is currently installed by the {@link GHelpSet}.
*
* @see GHelpSet
*/
public class GHelpHTMLEditorKit extends HTMLEditorKit {
private static final String G_HELP_STYLE_SHEET = "help/shared/Frontpage.css";
private static final Pattern EXTERNAL_URL_PATTERN = Pattern.compile("https?://.*");
/** A pattern to strip the font size value from a line of CSS */
private static final Pattern FONT_SIZE_PATTERN = Pattern.compile("font-size:\\s*(\\d{1,2})");
private static final String HELP_WINDOW_ZOOM_FACTOR = "HELP.WINDOW.FONT.SIZE.MODIFIER";
private static int fontSizeModifier;
private HyperlinkListener[] delegateListeners = null;
private HyperlinkListener resolverHyperlinkListener;
public GHelpHTMLEditorKit() {
fontSizeModifier =
Integer.valueOf(Preferences.getProperty(HELP_WINDOW_ZOOM_FACTOR, "0", true));
}
@Override
public ViewFactory getViewFactory() {
return new GHelpHTMLFactory();
}
@Override
public void install(JEditorPane c) {
super.install(c);
delegateListeners = c.getHyperlinkListeners();
for (HyperlinkListener listener : delegateListeners) {
c.removeHyperlinkListener(listener);
}
resolverHyperlinkListener = new ResolverHyperlinkListener();
c.addHyperlinkListener(resolverHyperlinkListener);
// add a listener to report trace information
c.addPropertyChangeListener(new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
String propertyName = evt.getPropertyName();
if ("page".equals(propertyName)) {
Msg.trace(this, "Page loaded: " + evt.getNewValue());
}
}
});
}
@Override
public void deinstall(JEditorPane c) {
c.removeHyperlinkListener(resolverHyperlinkListener);
for (HyperlinkListener listener : delegateListeners) {
c.addHyperlinkListener(listener);
}
super.deinstall(c);
}
private class ResolverHyperlinkListener implements HyperlinkListener {
@Override
public void hyperlinkUpdate(HyperlinkEvent e) {
if (delegateListeners == null) {
return;
}
if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
if (isExternalLink(e)) {
browseExternalLink(e);
return;
}
Msg.trace(this, "Link activated: " + e.getURL());
e = validateURL(e);
Msg.trace(this, "Validated event: " + e.getURL());
}
for (HyperlinkListener listener : delegateListeners) {
listener.hyperlinkUpdate(e);
}
}
}
private boolean isExternalLink(HyperlinkEvent e) {
String description = e.getDescription();
return description != null && EXTERNAL_URL_PATTERN.matcher(description).matches();
}
private void browseExternalLink(HyperlinkEvent e) {
String description = e.getDescription();
if (!Desktop.isDesktopSupported()) {
Msg.info(this, "Unable to launch external browser for " + description);
return;
}
try {
// use an external browser
URI uri = e.getURL().toURI();
Desktop.getDesktop().browse(uri);
}
catch (URISyntaxException | IOException e1) {
Msg.error(this, "Error browsing to external URL " + description, e1);
}
}
/**
* Tests the URL of the given event. If the URL is invalid, a new event may be created if
* a new, valid URL can be created. Creates a new event with a patched URL if
* the given event's URL is invalid.
*/
private HyperlinkEvent validateURL(HyperlinkEvent event) {
URL url = event.getURL();
try {
url.openStream();// assume that this will fail if the file does not exist
}
catch (IOException ioe) {
// assume this means that the url is invalid
Msg.trace(this, "URL of link is invalid: " + url.toExternalForm());
return maybeCreateNewHyperlinkEventWithUpdatedURL(event);
}
return event;// url is fine
}
/** Generates a new event with a URL based upon Ghidra's resources if needed. */
private HyperlinkEvent maybeCreateNewHyperlinkEventWithUpdatedURL(HyperlinkEvent event) {
Element element = event.getSourceElement();
if (element == null) {
return event;// this shouldn't happen since we were triggered from an A tag
}
AttributeSet a = element.getAttributes();
AttributeSet anchor = (AttributeSet) a.getAttribute(HTML.Tag.A);
if (anchor == null) {
return event;// this shouldn't happen since we were triggered from an A tag
}
String HREF = (String) anchor.getAttribute(HTML.Attribute.HREF);
Msg.trace(this, "HREF of <a> tag: " + HREF);
URL newUrl = getURLForHREFFromResources(HREF);
if (newUrl == null) {
return event;// unable to locate a resource by the name--bad link!
}
return new HyperlinkEvent(event.getSource(), event.getEventType(), newUrl,
event.getDescription(), event.getSourceElement());
}
private URL getURLForHREFFromResources(String originalHREF) {
int anchorIndex = originalHREF.indexOf("#");
String HREF = originalHREF;
String anchor = null;
if (anchorIndex != -1) {
HREF = HREF.substring(0, anchorIndex);
anchor = originalHREF.substring(anchorIndex);
}
// look for a URL using an installation environment setup...
URL newUrl = ResourceManager.getResource(HREF);
if (newUrl != null) {
return createURLWithAnchor(newUrl, anchor);
}
//
// The item was not found by the ResourceManager (i.e., it is not in a 'resources'
// directory). See if it may be a relative link to a build's installation root (like
// a file in <install dir>/docs).
//
newUrl = findApplicationfile(HREF);
return newUrl;
}
private URL createURLWithAnchor(URL anchorlessURL, String anchor) {
if (anchorlessURL == null) {
return anchorlessURL;
}
if (anchor == null) {
// nothing to do
return anchorlessURL;
}
try {
// put the anchor back into the URL
return new URL(anchorlessURL, anchor);
}
catch (MalformedURLException e) {
// shouldn't happen, since the file exists
Msg.showError(this, null, "Unexpected Error",
"Unexpected error creating a valid URL: " + anchorlessURL + "#" + anchor);
return null;
}
}
@Override
public void read(Reader in, Document doc, int pos) throws IOException, BadLocationException {
super.read(in, doc, pos);
HTMLDocument htmlDoc = (HTMLDocument) doc;
loadGHelpStyleSheet(htmlDoc);
}
private void loadGHelpStyleSheet(HTMLDocument doc) {
Reader reader = getGStyleSheetReader();
if (reader == null) {
return;
}
StyleSheet ss = doc.getStyleSheet();
try {
ss.loadRules(reader, null);
}
catch (IOException e) {
// shouldn't happen
Msg.debug(this, "Unable to load help style sheet");
}
}
private Reader getGStyleSheetReader() {
URL url = getGStyleSheetURL();
if (url == null) {
return null;
}
StringBuffer buffy = new StringBuffer();
try {
List<String> lines = FileUtilities.getLines(url);
for (String line : lines) {
changePixels(line, fontSizeModifier, buffy);
buffy.append('\n');
}
}
catch (IOException e) {
// shouldn't happen
Msg.debug(this, "Unable to read the lines of the help style sheet: " + url);
}
StringReader reader = new StringReader(buffy.toString());
return reader;
}
private void changePixels(String line, int amount, StringBuffer buffy) {
Matcher matcher = FONT_SIZE_PATTERN.matcher(line);
while (matcher.find()) {
String oldFontSize = matcher.group(1);
String adjustFontSize = adjustFontSize(oldFontSize);
matcher.appendReplacement(buffy, "font-size: " + adjustFontSize);
}
matcher.appendTail(buffy);
}
private String adjustFontSize(String sizeString) {
try {
int size = Integer.parseInt(sizeString);
String adjusted = Integer.toString(size + fontSizeModifier);
return adjusted;
}
catch (NumberFormatException e) {
Msg.debug(this, "Unable to parse font size string '" + sizeString + "'");
}
return sizeString;
}
private URL getGStyleSheetURL() {
URL GStyleSheetURL = ResourceManager.getResource(G_HELP_STYLE_SHEET);
if (GStyleSheetURL != null) {
return GStyleSheetURL;
}
return findModuleFile("help/shared/FrontPage.css");
}
private URL findApplicationfile(String relativePath) {
ResourceFile installDir = Application.getInstallationDirectory();
ResourceFile file = new ResourceFile(installDir, relativePath);
if (file.exists()) {
try {
return file.toURL();
}
catch (MalformedURLException e) {
Msg.showError(this, null, "Unexpected Error",
"Unexpected error parsing file to URL: " + file);
}
}
return null;
}
private URL findModuleFile(String relativePath) {
Collection<ResourceFile> moduleDirs = Application.getModuleRootDirectories();
for (ResourceFile dir : moduleDirs) {
ResourceFile file = new ResourceFile(dir, relativePath);
if (file.exists()) {
try {
return file.toURL();
}
catch (MalformedURLException e) {
Msg.showError(this, null, "Unexpected Error",
"Unexpected error parsing file to URL: " + file);
return null;
}
}
}
return null;
}
public static void zoomOut() {
fontSizeModifier -= 2;
saveZoomFactor();
}
public static void zoomIn() {
fontSizeModifier += 2;
saveZoomFactor();
}
private static void saveZoomFactor() {
Preferences.setProperty(HELP_WINDOW_ZOOM_FACTOR, Integer.toString(fontSizeModifier));
Preferences.store();
}
//==================================================================================================
// Inner Classes
//==================================================================================================
private class GHelpHTMLFactory extends HTMLFactory {
@Override
public View create(Element e) {
AttributeSet attributes = e.getAttributes();
Object elementName = attributes.getAttribute(AbstractDocument.ElementNameAttribute);
if (elementName != null) {
// not an HTML element
return super.create(e);
}
Object html = attributes.getAttribute(StyleConstants.NameAttribute);
if (html instanceof HTML.Tag) {
HTML.Tag tag = (Tag) html;
if (tag == HTML.Tag.IMG) {
return new GHelpImageView(e);
}
}
return super.create(e);
}
}
/**
* Overridden to allow us to find images that are defined as constants in places like
* {@link Icons}
*/
private class GHelpImageView extends ImageView {
/*
* Unusual Code Alert!
* This class exists to enable our help system to find custom icons defined in source
* code. The default behavior herein is to supply a URL to the base class to load. This
* works fine.
*
* There is another use case where we wish to have the base class load an image of our
* choosing. Why? Well, we modify, in memory, some icons we use. We do this for things
* like overlays and rotations.
*
* In order to have our base class use the image that we want (and not the one
* it loads via a URL), we have to play a small game. We have to allow the base class
* to load the image it wants, which is done asynchronously. If we install our custom
* image during that process, the loading will throw away the image and not render
* anything.
*
* To get the base class to use our image, we override getImage(). However, we should
* only return our image when the base class is finished loading. (See the base class'
* paint() method for why we need to do this.)
*
* Note: if we start seeing unusual behavior, like images not rendering, or any size
* issues, then we can revert this code.
*/
private Image image;
private float spanX;
private float spanY;
public GHelpImageView(Element elem) {
super(elem);
}
@Override
public Image getImage() {
Image superImage = super.getImage();
if (image == null) {
// no custom image
return superImage;
}
if (isLoading()) {
return superImage;
}
return image;
}
private boolean isLoading() {
return spanX < 1 || spanY < 1;
}
@Override
public float getPreferredSpan(int axis) {
float span = super.getPreferredSpan(axis);
if (axis == View.X_AXIS) {
spanX = span;
}
else {
spanY = span;
}
return span;
}
@Override
public URL getImageURL() {
AttributeSet attributes = getElement().getAttributes();
Object src = attributes.getAttribute(HTML.Attribute.SRC);
if (src == null) {
return null;
}
String srcString = src.toString();
if (isJavaCode(srcString)) {
return installImageFromJavaCode(srcString);
}
URL url = doGetImageURL(srcString);
return url;
}
private URL installImageFromJavaCode(String srcString) {
IconProvider iconProvider = getIconFromJavaCode(srcString);
if (iconProvider == null || iconProvider.isInvalid()) {
return null;
}
ImageIcon imageIcon = iconProvider.getIcon();
this.image = imageIcon.getImage();
URL url = iconProvider.getOrCreateUrl();
return url;
}
private URL doGetImageURL(String srcString) {
HTMLDocument htmlDocument = (HTMLDocument) getDocument();
URL context = htmlDocument.getBase();
try {
URL url = new URL(context, srcString);
if (FileUtilities.exists(url.toURI())) {
// it's a good one, let it through
return url;
}
}
catch (MalformedURLException | URISyntaxException e) {
// check below
}
// Try the ResourceManager. This will work for images that start with GHelp
// relative link syntax such as 'help/', 'help/topics/' and 'images/'
URL resource = ResourceManager.getResource(srcString);
return resource;
}
private boolean isJavaCode(String src) {
// not sure of the best way to handle this--be exact for now
return Icons.isIconsReference(src);
}
private IconProvider getIconFromJavaCode(String src) {
return Icons.getIconForIconsReference(src);
}
}
}

View file

@ -1,322 +0,0 @@
/* ###
* 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.help;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Enumeration;
import java.util.Map.Entry;
import java.util.Set;
import javax.help.*;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import generic.jar.ResourceFile;
import ghidra.framework.Application;
import ghidra.util.SystemUtilities;
/**
* Ghidra help set that creates a GhidraHelpBroker, installs some custom HTML handling code via
* the GHelpHTMLEditorKit, and most importantly, changes how the JavaHelp system works with
* regard to integrating Help Sets.
* <p>
* The HelpSet class uses a javax.help.Map object to locate HTML files by javax.help.map.ID objects.
* This class has overridden that basic usage of the Map object to allow ID lookups to take
* place across GHelpSet objects. We need to do this due to how we merge help set content
* across modules. More specifically, in order to merge, we have to make all {@code <tocitem>} xml tags
* the same, including the target HTML file they may reference. Well, when a module uses a
* {@code <tocitem>} tag that references an HTML file <b>not inside of it's module</b>, then JavaHelp
* considers this an error and does not correctly merge the HelpSets that share the reference.
* Further, it does not properly locate the shared HTML file reference. This class allows lookups
* across modules by overridden the lookup functionality done by the map object. More specifically,
* we override {@link #getCombinedMap()} and {@link #getLocalMap()} to use a custom delegate map
* object that knows how do do this "cross-module" help lookup.
*
*
*@see GHelpHTMLEditorKit
*/
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<>();
private Logger LOG = LogManager.getLogger(GHelpSet.class);
private GHelpMap combinedMapWrapper;
private GHelpMap localMapWrapper;
public GHelpSet(ClassLoader loader, URL helpset) throws HelpSetException {
super(loader, helpset);
init();
}
private void init() {
// swap in Ghidra's editor kit, which is an overridden version of Java's
String type = "text/html";
String editorKit = GHelpHTMLEditorKit.class.getName();
ClassLoader classLoader = getClass().getClassLoader();
setKeyData(kitTypeRegistry, type, editorKit);
setKeyData(kitLoaderRegistry, type, classLoader);
setHomeID(HOME_ID);
initializeCombinedMapWrapper();
}
@Override
public HelpBroker createHelpBroker() {
return new GHelpBroker(this);
}
@Override
public Map getLocalMap() {
Map localMap = super.getLocalMap();
if (localMap == null) {
return null;
}
initializeLocalMapWrapper();
return localMapWrapper;
}
private void initializeLocalMapWrapper() {
if (localMapWrapper == null) {
Map localMap = super.getLocalMap();
helpSetsToLocalMaps.put(this, localMap);
localMapWrapper = new GHelpMap(localMap);
}
}
@Override
public Map getCombinedMap() {
return combinedMapWrapper;
}
private void initializeCombinedMapWrapper() {
if (combinedMapWrapper == null) {
Map combinedMap = super.getCombinedMap();
helpSetsToCombinedMaps.put(this, combinedMap);
combinedMapWrapper = new GHelpMap(combinedMap);
}
}
//==================================================================================================
// Inner Classes
//==================================================================================================
/** A special class to allow us to handle help ID lookups across help sets */
private class GHelpMap implements Map {
private final Map mapDelegate;
private GHelpMap(Map mapDelegate) {
this.mapDelegate = mapDelegate;
}
@Override
public Enumeration<?> getAllIDs() {
return mapDelegate.getAllIDs();
}
@Override
public ID getClosestID(URL url) {
ID closestID = mapDelegate.getClosestID(url);
if (closestID != null) {
return closestID; // it's in our map
}
LOG.trace("Help Set \"" + GHelpSet.this + "\" does not contain ID for URL: " + url);
Set<Entry<HelpSet, Map>> entrySet = helpSetsToCombinedMaps.entrySet();
for (Entry<HelpSet, Map> entry : entrySet) {
Map map = entry.getValue();
closestID = map.getClosestID(url);
if (closestID != null) {
return closestID;
}
}
LOG.trace("No ID found in any HelpSet for URL: " + url);
return null;
}
@Override
public ID getIDFromURL(URL url) {
return mapDelegate.getIDFromURL(url);
}
@Override
public Enumeration<?> getIDs(URL url) {
return mapDelegate.getIDs(url);
}
@Override
public URL getURLFromID(ID id) throws MalformedURLException {
URL URL = mapDelegate.getURLFromID(id);
if (URL != null) {
return URL; // it's in our map
}
Set<Entry<HelpSet, Map>> entrySet = helpSetsToCombinedMaps.entrySet();
for (Entry<HelpSet, Map> entry : entrySet) {
Map map = entry.getValue();
URL = map.getURLFromID(id);
if (URL != null) {
return URL;
}
}
LOG.trace("No URL found in any HelpSet for ID: " + id);
URL = tryToCreateURLFromID(id.id);
if (URL != null) {
return URL;
}
return null;
}
/**
* This is meant for help files that are not included in the standard help system. Their
* id paths are expected to be relative to the application install directory.
* @param id the help id.
* @return the URL to the help file.
*/
private URL tryToCreateURLFromID(String id) {
URL fileURL = createFileURL(id);
if (fileURL != null) {
return fileURL;
}
URL rawURL = createRawURL(id);
return rawURL;
}
private URL createRawURL(String id) {
URL url = null;
try {
url = new URL(id);
}
catch (MalformedURLException e) {
LOG.trace("ID is not a URL; tried to make URL from string: " + id);
return null;
}
try {
InputStream inputStream = url.openStream();
inputStream.close();
return url; // it is valid
}
catch (IOException e) {
LOG.trace("ID is not a URL; unable to read URL: " + url);
}
return null;
}
private URL createFileURL(String id) {
ResourceFile helpFile = fileFromID(id);
if (!helpFile.exists()) {
LOG.trace("ID is not a file; tried: " + helpFile);
return null;
}
try {
return helpFile.toURL();
}
catch (MalformedURLException e) {
// this shouldn't happen, as the file exists
LOG.trace("ID is not a URL; tried to make URL from file: " + helpFile);
}
return null;
}
private ResourceFile fileFromID(String id) {
// this allows us to find files by using relative paths (e.g., 'docs/WhatsNew.html'
// will get resolved relative to the installation directory in a build).
ResourceFile installDir = Application.getInstallationDirectory();
ResourceFile helpFile = new ResourceFile(installDir, id);
return helpFile;
}
@Override
public boolean isID(URL url) {
return mapDelegate.isID(url);
}
@Override
public boolean isValidID(String id, HelpSet hs) {
HelpService service = Help.getHelpService();
if (!service.helpExists()) {
// Treat everything as valid until all help is loaded, otherwise, we
// can't be sure that when something is missing, it is just not yet merged in.
return true;
}
boolean isValid = mapDelegate.isValidID(id, hs);
if (isValid) {
return true;
}
Set<Entry<HelpSet, Map>> entrySet = helpSetsToCombinedMaps.entrySet();
for (Entry<HelpSet, Map> entry : entrySet) {
Map map = entry.getValue();
if (map.isValidID(id, hs)) {
return true;
}
}
// This can happen for help files that are generated during the build,
// such as 'What's New'; return true here so the values will still be loaded into
// the help system; handle the error condition later.
if (ignoreExternalHelp(id)) {
return true;
}
return false;
}
private boolean ignoreExternalHelp(String id) {
if (id.startsWith("help/topics")) {
return false; // not external help location
}
URL url = tryToCreateURLFromID(id);
if (url != null) {
return true; // ignore this id; it is valid
}
// no url for ID
if (SystemUtilities.isInDevelopmentMode()) {
// ignore external files that do not exist in dev mode
return true;
}
return false;
}
}
}

View file

@ -1,44 +0,0 @@
/* ###
* 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.help;
import docking.DefaultHelpService;
import docking.DockingWindowManager;
/**
* Creates the HelpManager for the application. This is just a glorified global variable for
* the application.
*/
public class Help {
private static HelpService helpService = new DefaultHelpService();
/**
* Get the help service
*
* @return null if the call to setMainHelpSetURL() failed
*/
public static HelpService getHelpService() {
return helpService;
}
// allows help services to install themselves
static void installHelpService(HelpService service) {
helpService = service;
DockingWindowManager.setHelpService(helpService);
}
}

View file

@ -38,6 +38,7 @@ import ghidra.util.*;
import ghidra.util.exception.AssertException;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
import help.*;
import resources.ResourceManager;
import utilities.util.reflection.ReflectionUtilities;
@ -83,7 +84,7 @@ public class HelpManager implements HelpService {
* @throws HelpSetException if HelpSet could not be created
*/
protected HelpManager(URL url) throws HelpSetException {
mainHS = new GHelpSet(new GHelpClassLoader(null), url);
mainHS = new DockingHelpSet(new GHelpClassLoader(null), url);
mainHB = mainHS.createHelpBroker();
mainHS.setTitle(GHIDRA_HELP_TITLE);
@ -260,8 +261,8 @@ public class HelpManager implements HelpService {
displayHelp(createHelpID(helpId), window);
}
catch (BadIDException e) {
Msg.info(this, "Could not find help for ID: \"" + helpId +
"\" from HelpLocation: " + loc);
Msg.info(this,
"Could not find help for ID: \"" + helpId + "\" from HelpLocation: " + loc);
displayHelp(HELP_NOT_FOUND_PAGE_URL, window);
}
}
@ -565,12 +566,12 @@ public class HelpManager implements HelpService {
/** This forces page to be redisplayed when location has not changed */
private void reloadPage(URL helpURL) {
if (!(mainHB instanceof GHelpBroker)) {
if (!(mainHB instanceof DockingHelpBroker)) {
// not our broker installed; can't force a reload
return;
}
((GHelpBroker) mainHB).reloadHelpPage(validateUrl(helpURL));
((DockingHelpBroker) mainHB).reloadHelpPage(validateUrl(helpURL));
}
private URL getURLForID(ID ID) {

View file

@ -1,116 +0,0 @@
/* ###
* 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.help;
import java.awt.Component;
import java.net.URL;
import ghidra.util.HelpLocation;
/**
* <code>HelpService</code> defines a service for displaying Help content by an ID or URL.
*/
public interface HelpService {
public static final String DUMMY_HELP_SET_NAME = "Dummy_HelpSet.hs";
/**
* Display the Help content identified by the help object.
*
* @param helpObject the object to which help was previously registered
* @param infoOnly display {@link HelpLocation} information only, not the help UI
* @param parent requesting component
*
* @see #registerHelp(Object, HelpLocation)
*/
public void showHelp(Object helpObject, boolean infoOnly, Component parent);
/**
* Display the help page for the given URL. This is a specialty method for displaying
* help when a specific file is desired, like an introduction page. Showing help for
* objects within the system is accomplished by calling
* {@link #showHelp(Object, boolean, Component)}.
*
* @param url the URL to display
* @see #showHelp(Object, boolean, Component)
*/
public void showHelp(URL url);
/**
* Display the help page for the given help location.
*
* @param location the location to display.
* @see #showHelp(Object, boolean, Component)
*/
public void showHelp(HelpLocation location);
/**
* Signals to the help system to ignore the given object when searching for and validating
* help. Once this method has been called, no help can be registered for the given object.
*
* @param helpObject the object to exclude from the help system.
*/
public void excludeFromHelp(Object helpObject);
/**
* Returns true if the given object is meant to be ignored by the help system
*
* @param helpObject the object to check
* @return true if ignored
* @see #excludeFromHelp(Object)
*/
public boolean isExcludedFromHelp(Object helpObject);
/**
* Register help for a specific object.
*
* <P>Do not call this method will a <code>null</code> help location. Instead, to signal that
* an item has no help, call {@link #excludeFromHelp(Object)}.
*
* @param helpObject the object to associate the specified help location with
* @param helpLocation help content location
*/
public void registerHelp(Object helpObject, HelpLocation helpLocation);
/**
* Removes this object from the help system. This method is useful, for example,
* when a single Java {@link Component} will have different help locations
* assigned over its lifecycle.
*
* @param helpObject the object for which to clear help
*/
public void clearHelp(Object helpObject);
/**
* Returns the registered (via {@link #registerHelp(Object, HelpLocation)} help
* location for the given object; null if there is no registered
* help.
*
* @param object The object for which to find a registered HelpLocation.
* @return the registered HelpLocation
* @see #registerHelp(Object, HelpLocation)
*/
public HelpLocation getHelpLocation(Object object);
/**
* Returns true if the help system has been initialized properly; false if help does not
* exist or is not working.
*
* @return true if the help system has found the applications help content and has finished
* initializing
*/
public boolean helpExists();
}

View file

@ -54,7 +54,7 @@ public class ToggleNavigationAid extends AbstractAction {
showingNavigationAid = Boolean.parseBoolean(value);
}
else {
// not yet in the preferences; save the default
// not yet in the preferences; save the default
savePreference();
}
}

View file

@ -23,11 +23,11 @@ import javax.swing.JButton;
import docking.ActionContext;
import docking.action.*;
import docking.help.Help;
import docking.widgets.EventTrigger;
import ghidra.util.HelpLocation;
import ghidra.util.Swing;
import ghidra.util.exception.AssertException;
import help.Help;
import resources.icons.EmptyIcon;
/**

View file

@ -24,12 +24,12 @@ import javax.swing.BorderFactory;
import javax.swing.JPanel;
import javax.swing.border.Border;
import docking.help.Help;
import docking.help.HelpService;
import ghidra.framework.options.*;
import ghidra.util.HelpLocation;
import ghidra.util.exception.AssertException;
import ghidra.util.layout.VerticalLayout;
import help.Help;
import help.HelpService;
/**
*

View file

@ -27,8 +27,6 @@ import javax.swing.*;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
import docking.help.Help;
import docking.help.HelpService;
import docking.widgets.MultiLineLabel;
import docking.widgets.OptionDialog;
import docking.widgets.label.GIconLabel;
@ -40,6 +38,8 @@ import ghidra.util.*;
import ghidra.util.bean.opteditor.OptionsVetoException;
import ghidra.util.layout.MiddleLayout;
import ghidra.util.task.SwingUpdateManager;
import help.Help;
import help.HelpService;
import resources.ResourceManager;
public class OptionsPanel extends JPanel {

View file

@ -29,7 +29,6 @@ import javax.swing.table.TableModel;
import org.jdom.Element;
import docking.DockingWindowManager;
import docking.help.HelpService;
import docking.menu.*;
import docking.widgets.EmptyBorderButton;
import docking.widgets.EventTrigger;
@ -45,6 +44,7 @@ import ghidra.util.datastruct.WeakDataStructureFactory;
import ghidra.util.datastruct.WeakSet;
import ghidra.util.exception.AssertException;
import ghidra.util.task.SwingUpdateManager;
import help.HelpService;
import resources.Icons;
import resources.ResourceManager;
import utilities.util.reflection.ReflectionUtilities;

View file

@ -22,10 +22,10 @@ import javax.swing.*;
import javax.swing.table.*;
import docking.DockingWindowManager;
import docking.help.HelpService;
import docking.widgets.table.columnfilter.ColumnBasedTableFilter;
import ghidra.util.HTMLUtilities;
import ghidra.util.HelpLocation;
import help.HelpService;
import resources.ResourceManager;
/**

View file

@ -23,7 +23,6 @@ import javax.swing.border.BevelBorder;
import org.jdom.Element;
import docking.DockingWindowManager;
import docking.help.HelpService;
import docking.widgets.EmptyBorderButton;
import docking.widgets.filter.*;
import docking.widgets.label.GLabel;
@ -32,6 +31,7 @@ import docking.widgets.tree.support.GTreeFilter;
import ghidra.framework.options.PreferenceState;
import ghidra.util.FilterTransformer;
import ghidra.util.HelpLocation;
import help.HelpService;
public class DefaultGTreeFilterProvider implements GTreeFilterProvider {
private static final String FILTER_STATE = "FILTER_STATE";

View file

@ -25,11 +25,11 @@ import javax.swing.border.TitledBorder;
import docking.DialogComponentProvider;
import docking.DockingWindowManager;
import docking.help.Help;
import docking.help.HelpService;
import docking.widgets.EmptyBorderButton;
import docking.widgets.label.GDLabel;
import ghidra.util.*;
import help.Help;
import help.HelpService;
import resources.ResourceManager;
/**