mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-03 09:49:23 +02:00
Merge remote-tracking branch 'origin/GP-5300_James_source_map_integration_improvements--SQUASHED' into Ghidra_11.3
This commit is contained in:
commit
6b24c09ea5
4 changed files with 319 additions and 10 deletions
|
@ -26,8 +26,8 @@
|
||||||
|
|
||||||
<BLOCKQUOTE>
|
<BLOCKQUOTE>
|
||||||
<OL>
|
<OL>
|
||||||
<LI>A path, which must be an absolute, normalized path using forward slashes (think file
|
<LI>A path, which must be an absolute, normalized path using forward slashes.
|
||||||
URI).</LI>
|
E.g., "/usr/src/main/file.c", "/C:/Users/Ghidra/sourceFile.cc".</LI>
|
||||||
|
|
||||||
<LI>A <I>SourceFileIdType</I>, which can be NONE, UNKNOWN, TIMESTAMP_64, MD5, SHA1, SHA256,
|
<LI>A <I>SourceFileIdType</I>, which can be NONE, UNKNOWN, TIMESTAMP_64, MD5, SHA1, SHA256,
|
||||||
or SHA512.</LI>
|
or SHA512.</LI>
|
||||||
|
@ -81,8 +81,9 @@
|
||||||
|
|
||||||
<BLOCKQUOTE>
|
<BLOCKQUOTE>
|
||||||
<P><IMG src="help/shared/note.png" alt="Note" border="0">Note that adding source files,
|
<P><IMG src="help/shared/note.png" alt="Note" border="0">Note that adding source files,
|
||||||
removing source files, or changing the source map requires exclusive access to a program.
|
removing source files, or changing the source map requires an exclusive checkout if
|
||||||
Reading the source file list or source map does not require exclusive access.</P>
|
the program is in a shared Ghidra repository. Reading the source file list or source map does
|
||||||
|
not require an exclusive checkout.</P>
|
||||||
<BR>
|
<BR>
|
||||||
</BLOCKQUOTE><BR>
|
</BLOCKQUOTE><BR>
|
||||||
<BR>
|
<BR>
|
||||||
|
@ -111,7 +112,8 @@
|
||||||
path.</LI>
|
path.</LI>
|
||||||
|
|
||||||
<LI><I>Directory Transforms</I>, which replace a parent directory of a source file's path
|
<LI><I>Directory Transforms</I>, which replace a parent directory of a source file's path
|
||||||
with another directory.</LI>
|
with another directory. For example, the directory transform "/src/ -> "/usr/test/files/"
|
||||||
|
would transform the path "/src/dir1/file.c" to "/usr/test/files/dir1/file.c".</LI>
|
||||||
</OL>
|
</OL>
|
||||||
</BLOCKQUOTE><BR>
|
</BLOCKQUOTE><BR>
|
||||||
<BR>
|
<BR>
|
||||||
|
@ -176,15 +178,54 @@
|
||||||
<P>This action removes the selected transform from the list of transforms.</P>
|
<P>This action removes the selected transform from the list of transforms.</P>
|
||||||
|
|
||||||
<H3><A name="Edit_Transform"></A>Edit Transform</H3>
|
<H3><A name="Edit_Transform"></A>Edit Transform</H3>
|
||||||
</BLOCKQUOTE>
|
|
||||||
|
|
||||||
<P>This action allows you to change the destination of a transform (but not the source).</P>
|
<P>This action allows you to change the destination of a transform (but not the source).</P>
|
||||||
|
</BLOCKQUOTE>
|
||||||
|
|
||||||
|
<H2><A name="Source_Files_Table_Plugin_Actions"></A>Listing Actions</H2>
|
||||||
|
|
||||||
|
<BLOCKQUOTE>
|
||||||
|
<H3><A name="View_Source"></A>View Source</H3>
|
||||||
|
|
||||||
|
<P>This Listing action is enabled if there is source map information
|
||||||
|
for an address. It will open the corresponding source file at the
|
||||||
|
appropriate line in a source code viewer (currently Eclipse and VS Code
|
||||||
|
are supported). Options for configuring the viewer are described
|
||||||
|
<A href="#Source_Files_Table_Plugin_Options"> below</A>. If there are
|
||||||
|
multiple source map entries defined for an address, the user will be
|
||||||
|
prompted to select which one to send to the viewer. Performing
|
||||||
|
this action on a particular line of the Source Map Listing field will
|
||||||
|
open the corresponding file in the viewer even if there are multiple
|
||||||
|
entries defined at the current address.
|
||||||
|
</P>
|
||||||
|
|
||||||
|
</BLOCKQUOTE>
|
||||||
|
|
||||||
|
|
||||||
|
<H2><A name="Source_Files_Table_Plugin_Options"></A>Plugin Options</H2>
|
||||||
|
|
||||||
|
<BLOCKQUOTE>
|
||||||
|
<H3><A name="Use_Existing_As_Default"></A>Use Existing As Default</H3>
|
||||||
|
|
||||||
|
<P>If enabled, the SourcePathTransformer will just return a SourceFile's path if
|
||||||
|
no transform applies to the file.</P>
|
||||||
|
|
||||||
|
<H3><A name="Viewer_for_Source_Files"></A>Viewer for Source Files</H3>
|
||||||
|
<P> Selects the viewer to use for source files. The supported viewers are Eclipse
|
||||||
|
and Visual Studio Code. Your viewer of choice must be configured via the
|
||||||
|
appropriate option in the Front-End tool.
|
||||||
|
|
||||||
|
</BLOCKQUOTE>
|
||||||
|
|
||||||
<P class="relatedtopic">Related Topics:</P>
|
<P class="relatedtopic">Related Topics:</P>
|
||||||
|
|
||||||
<UL>
|
<UL>
|
||||||
<LI><A href="help/topics/CodeBrowserPlugin/CodeBrowserOptions.htm#Source_Map_Field">Source
|
<LI><A href="help/topics/CodeBrowserPlugin/CodeBrowserOptions.htm#Source_Map_Field">Source
|
||||||
Map Field</A></LI>
|
Map Field</A></LI>
|
||||||
|
<LI><A href="help/topics/EclipseIntegration/EclipseIntegration.htm"> Eclipse Integration</A>
|
||||||
|
</LI>
|
||||||
|
<LI><A href="help/topics/VSCodeIntegration/VSCodeIntegration.htm"> Visual Studio Code
|
||||||
|
Integration</A></LI>
|
||||||
</UL><BR>
|
</UL><BR>
|
||||||
<BR>
|
<BR>
|
||||||
<BR>
|
<BR>
|
||||||
|
|
|
@ -22,7 +22,8 @@ import ghidra.docking.settings.Settings;
|
||||||
import ghidra.framework.plugintool.ServiceProvider;
|
import ghidra.framework.plugintool.ServiceProvider;
|
||||||
import ghidra.program.database.sourcemap.*;
|
import ghidra.program.database.sourcemap.*;
|
||||||
import ghidra.program.model.listing.Program;
|
import ghidra.program.model.listing.Program;
|
||||||
import ghidra.program.model.sourcemap.*;
|
import ghidra.program.model.sourcemap.SourceFileManager;
|
||||||
|
import ghidra.program.model.sourcemap.SourcePathTransformer;
|
||||||
import ghidra.util.datastruct.Accumulator;
|
import ghidra.util.datastruct.Accumulator;
|
||||||
import ghidra.util.exception.CancelledException;
|
import ghidra.util.exception.CancelledException;
|
||||||
import ghidra.util.task.TaskMonitor;
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
@ -150,6 +151,12 @@ public class SourceFilesTableModel extends ThreadedTableModelStub<SourceFileRowO
|
||||||
@Override
|
@Override
|
||||||
public String getValue(SourceFileRowObject rowObject, Settings settings, Object data,
|
public String getValue(SourceFileRowObject rowObject, Settings settings, Object data,
|
||||||
ServiceProvider sProvider) throws IllegalArgumentException {
|
ServiceProvider sProvider) throws IllegalArgumentException {
|
||||||
|
if (program == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (pathTransformer == null) {
|
||||||
|
pathTransformer = UserDataPathTransformer.getPathTransformer(program);
|
||||||
|
}
|
||||||
return pathTransformer.getTransformedPath(rowObject.getSourceFile(),
|
return pathTransformer.getTransformedPath(rowObject.getSourceFile(),
|
||||||
useExistingAsDefault);
|
useExistingAsDefault);
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,16 +17,38 @@ package ghidra.app.plugin.core.sourcefilestable;
|
||||||
|
|
||||||
import static ghidra.program.util.ProgramEvent.*;
|
import static ghidra.program.util.ProgramEvent.*;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import docking.DockingWindowManager;
|
||||||
|
import docking.action.builder.ActionBuilder;
|
||||||
|
import docking.options.editor.StringWithChoicesEditor;
|
||||||
|
import docking.widgets.values.GValuesMap;
|
||||||
|
import docking.widgets.values.ValuesMapDialog;
|
||||||
import ghidra.app.CorePluginPackage;
|
import ghidra.app.CorePluginPackage;
|
||||||
|
import ghidra.app.context.ListingActionContext;
|
||||||
import ghidra.app.events.ProgramClosedPluginEvent;
|
import ghidra.app.events.ProgramClosedPluginEvent;
|
||||||
import ghidra.app.plugin.PluginCategoryNames;
|
import ghidra.app.plugin.PluginCategoryNames;
|
||||||
import ghidra.app.plugin.ProgramPlugin;
|
import ghidra.app.plugin.ProgramPlugin;
|
||||||
|
import ghidra.app.services.EclipseIntegrationService;
|
||||||
|
import ghidra.app.services.VSCodeIntegrationService;
|
||||||
import ghidra.framework.model.*;
|
import ghidra.framework.model.*;
|
||||||
|
import ghidra.framework.options.*;
|
||||||
import ghidra.framework.plugintool.PluginInfo;
|
import ghidra.framework.plugintool.PluginInfo;
|
||||||
import ghidra.framework.plugintool.PluginTool;
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
import ghidra.framework.plugintool.util.PluginStatus;
|
import ghidra.framework.plugintool.util.PluginStatus;
|
||||||
|
import ghidra.program.database.sourcemap.UserDataPathTransformer;
|
||||||
|
import ghidra.program.model.address.Address;
|
||||||
import ghidra.program.model.listing.Program;
|
import ghidra.program.model.listing.Program;
|
||||||
|
import ghidra.program.model.sourcemap.*;
|
||||||
import ghidra.program.util.ProgramChangeRecord;
|
import ghidra.program.util.ProgramChangeRecord;
|
||||||
|
import ghidra.program.util.SourceMapFieldLocation;
|
||||||
|
import ghidra.util.HelpLocation;
|
||||||
|
import ghidra.util.Msg;
|
||||||
|
import ghidra.util.bean.opteditor.OptionsVetoException;
|
||||||
|
import ghidra.util.task.MonitoredRunnable;
|
||||||
|
import ghidra.util.task.TaskBuilder;
|
||||||
|
|
||||||
//@formatter:off
|
//@formatter:off
|
||||||
@PluginInfo(
|
@PluginInfo(
|
||||||
|
@ -43,11 +65,23 @@ import ghidra.program.util.ProgramChangeRecord;
|
||||||
* A {@link ProgramPlugin} for displaying source file information about a program
|
* A {@link ProgramPlugin} for displaying source file information about a program
|
||||||
* and for managing source file path transforms.
|
* and for managing source file path transforms.
|
||||||
*/
|
*/
|
||||||
public class SourceFilesTablePlugin extends ProgramPlugin {
|
public class SourceFilesTablePlugin extends ProgramPlugin implements OptionsChangeListener {
|
||||||
|
|
||||||
private SourceFilesTableProvider provider;
|
private SourceFilesTableProvider provider;
|
||||||
private DomainObjectListener listener;
|
private DomainObjectListener listener;
|
||||||
|
|
||||||
|
private static final String USE_EXISTING_AS_DEFAULT_OPTION_NAME =
|
||||||
|
"Use Existing Path as Default";
|
||||||
|
private boolean useExistingAsDefault = true;
|
||||||
|
|
||||||
|
private static final String ECLIPSE = "Eclipse";
|
||||||
|
private static final String VS_CODE = "VS Code";
|
||||||
|
private static final String[] VIEWERS = { ECLIPSE, VS_CODE };
|
||||||
|
private static final String SELECTED_VIEWER_OPTION_NAME = "Viewer for Source Files";
|
||||||
|
private String selectedViewer = VS_CODE;
|
||||||
|
private File vscodeExecutable = null;
|
||||||
|
private File eclipseExecutable = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
* @param plugintool tool
|
* @param plugintool tool
|
||||||
|
@ -61,6 +95,24 @@ public class SourceFilesTablePlugin extends ProgramPlugin {
|
||||||
super.init();
|
super.init();
|
||||||
provider = new SourceFilesTableProvider(this);
|
provider = new SourceFilesTableProvider(this);
|
||||||
listener = createDomainObjectListener();
|
listener = createDomainObjectListener();
|
||||||
|
initOptions(tool.getOptions("Source Files and Transforms"));
|
||||||
|
createAction();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void optionsChanged(ToolOptions options, String optionName, Object oldValue,
|
||||||
|
Object newValue) throws OptionsVetoException {
|
||||||
|
switch (optionName) {
|
||||||
|
case USE_EXISTING_AS_DEFAULT_OPTION_NAME:
|
||||||
|
useExistingAsDefault =
|
||||||
|
options.getBoolean(USE_EXISTING_AS_DEFAULT_OPTION_NAME, true);
|
||||||
|
break;
|
||||||
|
case SELECTED_VIEWER_OPTION_NAME:
|
||||||
|
selectedViewer = options.getString(SELECTED_VIEWER_OPTION_NAME, VS_CODE);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -95,4 +147,197 @@ public class SourceFilesTablePlugin extends ProgramPlugin {
|
||||||
.build();
|
.build();
|
||||||
// @formatter:on
|
// @formatter:on
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void createAction() {
|
||||||
|
new ActionBuilder("View Source", getName())
|
||||||
|
.popupMenuPath("View Source...")
|
||||||
|
.popupMenuGroup("ZZZ")
|
||||||
|
.helpLocation(new HelpLocation(getName(), "View_Source"))
|
||||||
|
.withContext(ListingActionContext.class)
|
||||||
|
.enabledWhen(c -> isEnabled(c))
|
||||||
|
.onAction(c -> viewSourceFile(c))
|
||||||
|
.buildAndInstall(tool);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isEnabled(ListingActionContext context) {
|
||||||
|
Address address = context.getAddress();
|
||||||
|
SourceFileManager manager = getCurrentProgram().getSourceFileManager();
|
||||||
|
return manager.getSourceMapEntries(address).size() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void viewSourceFile(ListingActionContext context) {
|
||||||
|
Address address = context.getAddress();
|
||||||
|
SourceFileManager manager = getCurrentProgram().getSourceFileManager();
|
||||||
|
List<SourceMapEntry> entries = manager.getSourceMapEntries(address);
|
||||||
|
if (entries.isEmpty()) {
|
||||||
|
return; // sanity check
|
||||||
|
}
|
||||||
|
// if there's only one entry associated with the address, just view it
|
||||||
|
if (entries.size() == 1) {
|
||||||
|
openInViewer(entries.get(0));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// if there are multiple entries, we need to decide which one to view
|
||||||
|
// if the user right-clicked in the SourceMapField in the Listing, open
|
||||||
|
// the associated entry
|
||||||
|
if (context.getLocation() instanceof SourceMapFieldLocation sourceLoc) {
|
||||||
|
openInViewer(sourceLoc.getSourceMapEntry());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// otherwise pop up a window and ask the user to select an entry
|
||||||
|
GValuesMap valuesMap = new GValuesMap();
|
||||||
|
Map<String, SourceMapEntry> stringsToEntries = new HashMap<>();
|
||||||
|
for (SourceMapEntry entry : entries) {
|
||||||
|
stringsToEntries.put(entry.toString(), entry);
|
||||||
|
}
|
||||||
|
valuesMap.defineChoice("Entry", entries.get(0).toString(),
|
||||||
|
stringsToEntries.keySet().toArray(new String[0]));
|
||||||
|
ValuesMapDialog dialog =
|
||||||
|
new ValuesMapDialog("Select Entry to View", null, valuesMap);
|
||||||
|
DockingWindowManager.showDialog(dialog);
|
||||||
|
if (dialog.isCancelled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
GValuesMap results = dialog.getValues();
|
||||||
|
if (results == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String selected = results.getChoice("Entry");
|
||||||
|
if (selected == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
SourceMapEntry entryToShow = stringsToEntries.get(selected);
|
||||||
|
openInViewer(entryToShow);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void openInViewer(SourceMapEntry entry) {
|
||||||
|
if (entry == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
SourcePathTransformer transformer =
|
||||||
|
UserDataPathTransformer.getPathTransformer(currentProgram);
|
||||||
|
String transformedPath =
|
||||||
|
transformer.getTransformedPath(entry.getSourceFile(), useExistingAsDefault);
|
||||||
|
|
||||||
|
if (transformedPath == null) {
|
||||||
|
Msg.showWarn(this, null, "No Path Transform",
|
||||||
|
"No path transformation applies to " + entry.getSourceFile().toString());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
File localSourceFile = new File(transformedPath);
|
||||||
|
if (!localSourceFile.exists()) {
|
||||||
|
Msg.showWarn(transformer, null, "File Not Found",
|
||||||
|
localSourceFile.getAbsolutePath() + " does not exist");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (selectedViewer) {
|
||||||
|
case ECLIPSE:
|
||||||
|
openFileInEclipse(localSourceFile.getAbsolutePath(), entry.getLineNumber());
|
||||||
|
break;
|
||||||
|
case VS_CODE:
|
||||||
|
openFileInVsCode(localSourceFile.getAbsolutePath(), entry.getLineNumber());
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new AssertionError("Unsupported Viewer: " + selectedViewer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void openFileInEclipse(String path, int lineNumber) {
|
||||||
|
EclipseIntegrationService eclipseService = tool.getService(EclipseIntegrationService.class);
|
||||||
|
if (eclipseService == null) {
|
||||||
|
Msg.showError(this, null, "Eclipse Service Error",
|
||||||
|
"Eclipse service not configured for tool");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
eclipseExecutable = eclipseService.getEclipseExecutableFile();
|
||||||
|
}
|
||||||
|
catch (FileNotFoundException e) {
|
||||||
|
Msg.showError(this, null, "Missing Eclipse Executable", e.getMessage());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
MonitoredRunnable r = m -> {
|
||||||
|
try {
|
||||||
|
List<String> args = new ArrayList<>();
|
||||||
|
args.add(eclipseExecutable.getAbsolutePath());
|
||||||
|
args.add(path + ":" + lineNumber);
|
||||||
|
new ProcessBuilder(args).redirectErrorStream(true).start();
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
eclipseService.handleEclipseError(
|
||||||
|
"Unexpected exception occurred while launching Eclipse.", false,
|
||||||
|
null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
new TaskBuilder("Opening File in Eclipse", r)
|
||||||
|
.setHasProgress(false)
|
||||||
|
.setCanCancel(true)
|
||||||
|
.launchModal();
|
||||||
|
return;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void openFileInVsCode(String path, int lineNumber) {
|
||||||
|
VSCodeIntegrationService vscodeService = tool.getService(VSCodeIntegrationService.class);
|
||||||
|
if (vscodeService == null) {
|
||||||
|
Msg.showError(this, null, "VSCode Service Error",
|
||||||
|
"VSCode service not configured for tool");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
vscodeExecutable = vscodeService.getVSCodeExecutableFile();
|
||||||
|
}
|
||||||
|
catch (FileNotFoundException e) {
|
||||||
|
Msg.showError(this, null, "Missing VSCode executable", e.getMessage());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
MonitoredRunnable r = m -> {
|
||||||
|
try {
|
||||||
|
List<String> args = new ArrayList<>();
|
||||||
|
args.add(vscodeExecutable.getAbsolutePath());
|
||||||
|
args.add("--goto");
|
||||||
|
args.add(path + ":" + lineNumber);
|
||||||
|
new ProcessBuilder(args).redirectErrorStream(true).start();
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
vscodeService.handleVSCodeError(
|
||||||
|
"Unexpected exception occurred while launching Visual Studio Code.", false,
|
||||||
|
null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
new TaskBuilder("Opening File in VSCode", r)
|
||||||
|
.setHasProgress(false)
|
||||||
|
.setCanCancel(true)
|
||||||
|
.launchModal();
|
||||||
|
return;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void initOptions(ToolOptions options) {
|
||||||
|
options.registerOption(USE_EXISTING_AS_DEFAULT_OPTION_NAME, true,
|
||||||
|
new HelpLocation(getName(), "Use_Existing_As_Default"),
|
||||||
|
"Use a source file's existing path if no transform applies");
|
||||||
|
useExistingAsDefault = options.getBoolean(USE_EXISTING_AS_DEFAULT_OPTION_NAME, true);
|
||||||
|
|
||||||
|
options.registerOption(SELECTED_VIEWER_OPTION_NAME,
|
||||||
|
OptionType.STRING_TYPE, VS_CODE,
|
||||||
|
new HelpLocation(getName(), "Viewer_for_Source_Files"),
|
||||||
|
"Viewer for Source Files",
|
||||||
|
() -> new StringWithChoicesEditor(VIEWERS));
|
||||||
|
selectedViewer = options.getString(SELECTED_VIEWER_OPTION_NAME, VS_CODE);
|
||||||
|
options.addOptionsChangeListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,9 +4,9 @@
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@ -591,6 +591,22 @@ public class FormatManager implements OptionsChangeListener {
|
||||||
rowElem.addContent(colElem);
|
rowElem.addContent(colElem);
|
||||||
|
|
||||||
root.addContent(rowElem);
|
root.addContent(rowElem);
|
||||||
|
|
||||||
|
rowElem = new Element("ROW");
|
||||||
|
|
||||||
|
colElem = new Element("FIELD");
|
||||||
|
colElem.setAttribute("WIDTH", "200");
|
||||||
|
colElem.setAttribute("ENABLED", "true");
|
||||||
|
rowElem.addContent(colElem);
|
||||||
|
|
||||||
|
colElem = new Element("FIELD");
|
||||||
|
colElem.setAttribute("NAME", "Source Map");
|
||||||
|
colElem.setAttribute("WIDTH", "440");
|
||||||
|
colElem.setAttribute("ENABLED", "true");
|
||||||
|
rowElem.addContent(colElem);
|
||||||
|
|
||||||
|
root.addContent(rowElem);
|
||||||
|
|
||||||
rowElem = new Element("ROW");
|
rowElem = new Element("ROW");
|
||||||
|
|
||||||
colElem = new Element("FIELD");
|
colElem = new Element("FIELD");
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue