mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-08-18 08:22:06 +08:00
GP-7114 - Search Text - Fixed searching of clipped text; fixed plate comment searching
This commit is contained in:
+261
-50
@@ -19,6 +19,7 @@ import java.awt.*;
|
||||
import java.math.BigInteger;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@@ -29,9 +30,10 @@ import org.apache.commons.lang3.StringUtils;
|
||||
import docking.*;
|
||||
import docking.action.builder.ActionBuilder;
|
||||
import docking.tool.ToolConstants;
|
||||
import docking.widgets.fieldpanel.support.FieldLocation;
|
||||
import docking.widgets.fieldpanel.support.Highlight;
|
||||
import docking.widgets.fieldpanel.field.TextField;
|
||||
import docking.widgets.fieldpanel.support.*;
|
||||
import docking.widgets.table.threaded.*;
|
||||
import generic.json.Json;
|
||||
import generic.theme.GIcon;
|
||||
import ghidra.app.CorePluginPackage;
|
||||
import ghidra.app.context.*;
|
||||
@@ -141,7 +143,7 @@ public class SearchTextPlugin extends ProgramPlugin implements OptionsChangeList
|
||||
|
||||
searchDialog.setStatusText("");
|
||||
SearchTask searchTask = (SearchTask) task;
|
||||
Navigatable searchNavigatable = ((SearchTask) task).getNavigatable();
|
||||
Navigatable searchNavigatable = searchTask.getNavigatable();
|
||||
Program program = ((SearchTask) task).getProgram();
|
||||
if (searchNavigatable.getProgram() == null || searchNavigatable.isDisposed()) {
|
||||
return;
|
||||
@@ -150,28 +152,38 @@ public class SearchTextPlugin extends ProgramPlugin implements OptionsChangeList
|
||||
TextSearchResult result = searchTask.getSearchLocation();
|
||||
Searcher textSearcher = searchTask.getTextSearcher();
|
||||
SearchOptions searchOptions = textSearcher.getSearchOptions();
|
||||
if (result == null) {
|
||||
searchDialog.setStatusText("Not found");
|
||||
}
|
||||
else {
|
||||
searchDialog.setStatusText("");
|
||||
ProgramLocation loc = result.programLocation();
|
||||
if (goToService.goTo(searchNavigatable, loc, program)) {
|
||||
new SearchTextHighlightProvider(searchNavigatable, searchOptions, null, program,
|
||||
result);
|
||||
|
||||
// The navigatable may change its location if it does not have the search result
|
||||
// location visible. We store that here so we can later detect that case in order
|
||||
// to keep the search from getting stuck.
|
||||
ProgramLocation navigatableLoc = navigatable.getLocation();
|
||||
lastSearchHit = new LastSearchHit(loc, navigatableLoc);
|
||||
}
|
||||
}
|
||||
|
||||
lastSearchedText = searchOptions.getText();
|
||||
if (task == currentTask) {
|
||||
// The current task is used to prevent the program from closing and for cancelling. If
|
||||
// they do not match, then a new search task has been started before this one finished.
|
||||
currentTask = null;
|
||||
}
|
||||
|
||||
if (result == null) {
|
||||
searchDialog.setStatusText("Not found");
|
||||
return;
|
||||
}
|
||||
|
||||
ProgramLocation loc = result.programLocation();
|
||||
if (lastSearchHit != null && lastSearchHit.shouldSkipHiddenMatch(loc)) {
|
||||
searchAgainFromHiddenLocation(loc);
|
||||
return;
|
||||
}
|
||||
|
||||
searchDialog.setStatusText("");
|
||||
if (!goToService.goTo(searchNavigatable, loc, program)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// The navigatable may change its location if it does not have the search result
|
||||
// location visible. We store that here so we can later detect that case in order
|
||||
// to keep the search from getting stuck.
|
||||
ProgramLocation navigatableLoc = navigatable.getLocation();
|
||||
String searchText = searchOptions.getText();
|
||||
lastSearchHit = new LastSearchHit(searchText, loc, navigatableLoc);
|
||||
|
||||
new SearchTextHighlightProvider(searchNavigatable, searchOptions, null, program, result);
|
||||
}
|
||||
|
||||
String getLastSearchText() {
|
||||
@@ -248,43 +260,49 @@ public class SearchTextPlugin extends ProgramPlugin implements OptionsChangeList
|
||||
searchDialog.setHasSelection(selection != null && !selection.isEmpty());
|
||||
}
|
||||
|
||||
void next() {
|
||||
|
||||
ProgramLocation loc = getStartLocation();
|
||||
Searcher textSearcher = null;
|
||||
SearchOptions options = searchDialog.getSearchOptions();
|
||||
AddressSetView addrs = getAddressSet(navigatable, options);
|
||||
|
||||
TaskMonitor monitor =
|
||||
searchDialog.showTaskMonitorComponent(AbstractSearchTableModel.TITLE, true, true);
|
||||
Program program = navigatable.getProgram();
|
||||
if (options.isProgramDatabaseSearch()) {
|
||||
textSearcher = new ProgramDatabaseSearcher(tool, program, loc, addrs, options, monitor);
|
||||
}
|
||||
else {
|
||||
textSearcher = new ListingDisplaySearcher(tool, program, loc, addrs, options, monitor);
|
||||
}
|
||||
searchNext(navigatable.getProgram(), navigatable, textSearcher);
|
||||
private void searchAgainFromHiddenLocation(ProgramLocation loc) {
|
||||
lastSearchHit = lastSearchHit.createHiddenSearchHit(loc);
|
||||
next();
|
||||
}
|
||||
|
||||
private ProgramLocation getStartLocation() {
|
||||
void next() {
|
||||
|
||||
ProgramLocation loc = getSearchStartLocation();
|
||||
SearchOptions options = searchDialog.getSearchOptions();
|
||||
AddressSetView addrs = getAddressSet(navigatable, options);
|
||||
String title = AbstractSearchTableModel.TITLE;
|
||||
TaskMonitor monitor = searchDialog.showTaskMonitorComponent(title, true, true);
|
||||
Program p = navigatable.getProgram();
|
||||
Searcher searcher = null;
|
||||
if (options.isProgramDatabaseSearch()) {
|
||||
searcher = new ProgramDatabaseSearcher(tool, p, loc, addrs, options, monitor);
|
||||
}
|
||||
else {
|
||||
searcher = new ListingDisplaySearcher(tool, p, loc, addrs, options, monitor);
|
||||
}
|
||||
doSearchNext(navigatable.getProgram(), navigatable, searcher);
|
||||
}
|
||||
|
||||
private ProgramLocation getSearchStartLocation() {
|
||||
|
||||
ProgramLocation currentNavLoc = navigatable.getLocation();
|
||||
if (lastSearchHit != null) {
|
||||
ProgramLocation lastNavLoc = lastSearchHit.navigatableLocation();
|
||||
ProgramLocation lastNavLoc = lastSearchHit.getNavigatableLocation();
|
||||
|
||||
// The navigatable's location has not changed since the last search. Use the last search
|
||||
// location as the start point for the next search. This ensures the searching does not
|
||||
// get stuck when the navigatable cannot display the last search hit.
|
||||
if (lastNavLoc.equals(currentNavLoc)) {
|
||||
return lastSearchHit.searchLocation();
|
||||
return lastSearchHit.getSearchLocation();
|
||||
}
|
||||
lastSearchHit = null;
|
||||
}
|
||||
|
||||
return currentNavLoc;
|
||||
}
|
||||
|
||||
private void searchNext(Program program, Navigatable searchNavigatable, Searcher textSearcher) {
|
||||
private void doSearchNext(Program program, Navigatable searchNavigatable,
|
||||
Searcher textSearcher) {
|
||||
SearchTask task = new SearchTask(searchNavigatable, program, textSearcher);
|
||||
task.addTaskListener(this);
|
||||
currentTask = task;
|
||||
@@ -553,9 +571,103 @@ public class SearchTextPlugin extends ProgramPlugin implements OptionsChangeList
|
||||
// Inner Classes
|
||||
//==================================================================================================
|
||||
|
||||
private record LastSearchHit(ProgramLocation searchLocation,
|
||||
ProgramLocation navigatableLocation) {
|
||||
//
|
||||
private class LastSearchHit {
|
||||
|
||||
private String searchText;
|
||||
private ProgramLocation searchLocation;
|
||||
private ProgramLocation navigatableLocation;
|
||||
|
||||
private boolean isHidden;
|
||||
private int ellipsisStart;
|
||||
|
||||
LastSearchHit(String searchText, ProgramLocation searchLocation,
|
||||
ProgramLocation navigatableLocation) {
|
||||
this.searchText = searchText;
|
||||
this.searchLocation = searchLocation;
|
||||
this.navigatableLocation = navigatableLocation;
|
||||
}
|
||||
|
||||
LastSearchHit createHiddenSearchHit(ProgramLocation loc) {
|
||||
|
||||
LastSearchHit newSearchHit = new LastSearchHit(searchText, loc, navigatableLocation);
|
||||
newSearchHit.isHidden = true;
|
||||
newSearchHit.ellipsisStart = ellipsisStart;
|
||||
return newSearchHit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this search location is hidden and the given location is also in the
|
||||
* clipped text area.
|
||||
* @param loc the current search result location
|
||||
* @return true to skip a hidden location
|
||||
*/
|
||||
boolean shouldSkipHiddenMatch(ProgramLocation loc) {
|
||||
if (!isHidden) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Class<? extends ProgramLocation> myClass = searchLocation.getClass();
|
||||
Class<? extends ProgramLocation> otherClass = loc.getClass();
|
||||
if (myClass != otherClass) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Address myAddress = searchLocation.getAddress();
|
||||
Address otherAddress = loc.getAddress();
|
||||
if (!myAddress.equals(otherAddress)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// assume text is clipped per row
|
||||
int myRow = searchLocation.getRow();
|
||||
int otherRow = loc.getRow();
|
||||
if (myRow != otherRow) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// skip all text past the ellipsis, as it is not being rendered
|
||||
int matchStart = loc.getCharOffset();
|
||||
return matchStart > ellipsisStart;
|
||||
}
|
||||
|
||||
ProgramLocation getSearchLocation() {
|
||||
return searchLocation;
|
||||
}
|
||||
|
||||
ProgramLocation getNavigatableLocation() {
|
||||
return navigatableLocation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals that this search result cannot be seen in the UI, such as when the content is
|
||||
* in a field that has been clipped. This is called after the search when highlighting is
|
||||
* taking place. We need to do this at that point because the highlighter is passed all the
|
||||
* info it needs to see if the search result is hidden.
|
||||
* @param hlSearchText the current highlight search text; used to know if the highlighter
|
||||
* is working on the active search
|
||||
* @param ellipsisOffset the offset of the ellipsis text
|
||||
*/
|
||||
void markHidden(String hlSearchText, int ellipsisOffset) {
|
||||
|
||||
if (!Objects.equals(searchText, hlSearchText)) {
|
||||
return; // highlighting an older search hit
|
||||
}
|
||||
|
||||
if (isHidden) {
|
||||
return;
|
||||
}
|
||||
|
||||
isHidden = true;
|
||||
this.ellipsisStart = ellipsisOffset;
|
||||
if (searchDialog != null) {
|
||||
searchDialog.setStatusText("Search result hidden", MessageType.WARNING);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return Json.toString(this);
|
||||
}
|
||||
}
|
||||
|
||||
private class TableLoadingListener implements ThreadedTableModelListener {
|
||||
@@ -625,6 +737,9 @@ public class SearchTextPlugin extends ProgramPlugin implements OptionsChangeList
|
||||
|
||||
private class SearchTextHighlightProvider
|
||||
implements ListingHighlightProvider, ComponentProviderActivationListener {
|
||||
|
||||
private static final String ELLIPSIS = "...";
|
||||
|
||||
private SearchOptions searchOptions;
|
||||
private TableComponentProvider<?> provider;
|
||||
private Program highlightProgram;
|
||||
@@ -655,10 +770,6 @@ public class SearchTextPlugin extends ProgramPlugin implements OptionsChangeList
|
||||
|
||||
Class<? extends FieldFactory> fieldFactoryClass = field.getFieldFactory().getClass();
|
||||
|
||||
if (!doHighlight) {
|
||||
return NO_HIGHLIGHTS;
|
||||
}
|
||||
|
||||
if (checkRemoveHighlights()) {
|
||||
return NO_HIGHLIGHTS;
|
||||
}
|
||||
@@ -687,6 +798,10 @@ public class SearchTextPlugin extends ProgramPlugin implements OptionsChangeList
|
||||
|
||||
private Highlight[] getAllHighlights(String text, int cursorTextOffset) {
|
||||
|
||||
if (!doHighlight) {
|
||||
return NO_HIGHLIGHTS;
|
||||
}
|
||||
|
||||
String searchText = searchOptions.getText();
|
||||
if (StringUtils.isBlank(searchText) || StringUtils.isBlank(text)) {
|
||||
return NO_HIGHLIGHTS;
|
||||
@@ -745,8 +860,9 @@ public class SearchTextPlugin extends ProgramPlugin implements OptionsChangeList
|
||||
*/
|
||||
int row = fieldLocation.getRow();
|
||||
int col = fieldLocation.getCol();
|
||||
int screenOffset = field.screenLocationToTextOffset(row, col);
|
||||
int searchStart = screenOffset;
|
||||
int fullTextOffset = field.screenLocationToTextOffset(row, col);
|
||||
|
||||
int searchStart = fullTextOffset;
|
||||
int searchEnd = searchStart + searchText.length();
|
||||
|
||||
Color hlColor = SearchConstants.SEARCH_HIGHLIGHT_COLOR;
|
||||
@@ -755,11 +871,106 @@ public class SearchTextPlugin extends ProgramPlugin implements OptionsChangeList
|
||||
hlColor = SearchConstants.SEARCH_HIGHLIGHT_CURRENT_ADDR_COLOR;
|
||||
}
|
||||
|
||||
// Note: we still want to call these methods that handle clipped text, even when not
|
||||
// highlighting (see the method notes).
|
||||
|
||||
// this handles fields that do their own clipping internally, like ClippingTextField
|
||||
Highlight ellipsisHl =
|
||||
createClippedDisplayHighlight(field, searchText, row, col, fullTextOffset);
|
||||
|
||||
if (ellipsisHl == null) {
|
||||
// this handled fields that are clipped by the field factory, like PlateFieldFactory
|
||||
ellipsisHl =
|
||||
createClippedModelHighlight(field, text, searchText, fullTextOffset);
|
||||
}
|
||||
|
||||
if (!doHighlight) {
|
||||
return NO_HIGHLIGHTS;
|
||||
}
|
||||
|
||||
if (ellipsisHl != null) {
|
||||
return new Highlight[] { ellipsisHl };
|
||||
}
|
||||
|
||||
// this is the matching search hit for a single search
|
||||
int endInc = searchEnd - 1; // highlights are inclusive
|
||||
return new Highlight[] { new Highlight(searchStart, endInc, hlColor) };
|
||||
}
|
||||
|
||||
// Note: this method performs double-duty: it not only creates a highlight for text that
|
||||
// is replaced with an ellipsis, it also updates the state of the plugin to track that the
|
||||
// last search hit is a hidden search hit. Thus, even if highlighting is off, we still need
|
||||
// to perform this work for the searching to perform optimally when ignoring hidden results.
|
||||
private Highlight createClippedDisplayHighlight(ListingField field,
|
||||
String searchText, int row, int col, int fullTextOffset) {
|
||||
|
||||
if (!(field instanceof TextField tf)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
boolean isClipped = tf.isClipped();
|
||||
if (!isClipped) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int numCols = tf.getNumCols(row);
|
||||
int fieldEnd = numCols - 1; // -1 for zero based indexing
|
||||
int clippedCol = col;
|
||||
if (clippedCol != fieldEnd) {
|
||||
// Assume the hit landing at the end is required for a hidden result. Search results
|
||||
// are normally placed at the beginning of the text.
|
||||
return null;
|
||||
}
|
||||
|
||||
// signal to have the next search skip any more hidden text hits
|
||||
lastSearchHit.markHidden(searchText, clippedCol);
|
||||
|
||||
// A normal highlight will work for fields that can paint their own clipping. These
|
||||
// fields will paint the '...' and a highlight on the ellipsis.
|
||||
int start = fullTextOffset;
|
||||
int end = fullTextOffset + ELLIPSIS.length();
|
||||
Color hlColor = SearchConstants.SEARCH_HIGHLIGHT_COLOR;
|
||||
return new Highlight(start, end, hlColor);
|
||||
}
|
||||
|
||||
// Note: this method performs double-duty: it not only creates a highlight for text that
|
||||
// is replaced with an ellipsis, it also updates the state of the plugin to track that the
|
||||
// last search hit is a hidden search hit. Thus, even if highlighting is off, we still need
|
||||
// to perform this work for the searching to perform optimally when ignoring hidden results.
|
||||
private Highlight createClippedModelHighlight(ListingField field, String fieldText,
|
||||
String searchText, int screenOffset) {
|
||||
|
||||
int len = ELLIPSIS.length();
|
||||
int contextStart = screenOffset - len;
|
||||
int contextEnd = contextStart + (len * 2);
|
||||
|
||||
if (contextStart < 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String context = fieldText.substring(contextStart, contextEnd);
|
||||
int relativeIndex = context.indexOf(ELLIPSIS);
|
||||
if (relativeIndex < 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (searchText.contains(ELLIPSIS)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Color hlColor = SearchConstants.SEARCH_HIGHLIGHT_COLOR;
|
||||
int absoluteIndex = contextStart + relativeIndex;
|
||||
Highlight ellipsisHl = new Highlight(absoluteIndex, absoluteIndex + len, hlColor);
|
||||
|
||||
// this fields search result is hidden behind truncated text
|
||||
int screenEllipsisOffset = ellipsisHl.getStart();
|
||||
RowColLocation modelLoc = field.textOffsetToScreenLocation(screenEllipsisOffset);
|
||||
int modelEllipsisOffset = modelLoc.col();
|
||||
lastSearchHit.markHidden(searchText, modelEllipsisOffset);
|
||||
|
||||
return ellipsisHl;
|
||||
}
|
||||
|
||||
private boolean shouldHighlight(ListingField field) {
|
||||
|
||||
ProxyObj<?> proxy = field.getProxy();
|
||||
|
||||
+14
-4
@@ -534,12 +534,22 @@ public class EolCommentFieldFactory extends FieldFactory {
|
||||
codeUnitFormatOptions.followReferencedPointers(), maxDisplayLines, extraCommentsOption);
|
||||
|
||||
ListingTextField btf = (ListingTextField) bf;
|
||||
RowColLocation eolRowCol = displayableEol.getRowCol((CommentFieldLocation) loc);
|
||||
RowColLocation rcl = btf.dataToScreenLocation(eolRowCol.row(), eolRowCol.col());
|
||||
if (!hasSamePath(bf, loc)) {
|
||||
return null;
|
||||
}
|
||||
return new FieldLocation(index, fieldNum, rcl.row(), rcl.col());
|
||||
|
||||
RowColLocation eolRowCol = displayableEol.getRowCol((CommentFieldLocation) loc);
|
||||
int row = eolRowCol.row();
|
||||
int col = eolRowCol.col();
|
||||
RowColLocation screenRowCol = btf.dataToScreenLocation(row, col);
|
||||
|
||||
int screenRow = screenRowCol.row();
|
||||
int screenCol = screenRowCol.col();
|
||||
if (screenRowCol.isHidden()) { // clipped location
|
||||
screenCol = btf.getNumCols(screenRow) - 1; // -1 for zero based indexing
|
||||
}
|
||||
|
||||
return new FieldLocation(index, fieldNum, screenRow, screenCol);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -571,8 +581,8 @@ public class EolCommentFieldFactory extends FieldFactory {
|
||||
if (comments.length == 0) {
|
||||
return null;
|
||||
}
|
||||
StringBuffer buf = new StringBuffer(comments[0]);
|
||||
|
||||
StringBuilder buf = new StringBuilder(comments[0]);
|
||||
for (int i = 1; i < comments.length; i++) {
|
||||
buf.append(separatorChar + comments[i]);
|
||||
}
|
||||
|
||||
+5
-5
@@ -227,8 +227,8 @@ abstract class OperandFieldHelper extends FieldFactory {
|
||||
return null;
|
||||
}
|
||||
|
||||
ListingTextField btf = (ListingTextField) lf;
|
||||
FieldElement fieldElement = btf.getFieldElement(row, col);
|
||||
ListingTextField ltf = (ListingTextField) lf;
|
||||
FieldElement fieldElement = ltf.getFieldElement(row, col);
|
||||
if (!(fieldElement instanceof OperandFieldElement)) {
|
||||
return null;
|
||||
}
|
||||
@@ -236,7 +236,7 @@ abstract class OperandFieldHelper extends FieldFactory {
|
||||
OperandFieldElement element = (OperandFieldElement) fieldElement;
|
||||
int opIndex = element.getOperandIndex();
|
||||
int subOpIndex = element.getOperandSubIndex();
|
||||
RowColLocation translatedLocation = btf.screenToDataLocation(row, col);
|
||||
RowColLocation translatedLocation = ltf.screenToDataLocation(row, col);
|
||||
|
||||
int dataCol = translatedLocation.col();
|
||||
if (obj instanceof Instruction) {
|
||||
@@ -935,8 +935,8 @@ abstract class OperandFieldHelper extends FieldFactory {
|
||||
|
||||
@Override
|
||||
public FieldElement replaceAll(char[] targets, char replacement) {
|
||||
return new OperandFieldElement(attributedString.replaceAll(targets, replacement), row,
|
||||
operandSubIndex, column);
|
||||
AttributedString as = attributedString.replaceAll(targets, replacement);
|
||||
return new OperandFieldElement(as, row, operandSubIndex, column);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+136
-22
@@ -241,6 +241,8 @@ public class PlateFieldFactory extends FieldFactory {
|
||||
// add top border
|
||||
elements.add(new TextFieldElement(asteriscs, row++, 0));
|
||||
|
||||
int rowOffset = elements.size(); // any header lines and our top border
|
||||
|
||||
// add and word wrap the comments
|
||||
List<FieldElement> commentsList = new ArrayList<>();
|
||||
for (String c : comments) {
|
||||
@@ -260,7 +262,7 @@ public class PlateFieldFactory extends FieldFactory {
|
||||
commentsList = FieldUtils.wrap(commentsList, Math.max(width - paddingWidth, charWidth));
|
||||
}
|
||||
|
||||
boolean isClipped = addSideBorders(commentsList);
|
||||
boolean isClipped = addSideBorders(commentsList, rowOffset);
|
||||
elements.addAll(commentsList);
|
||||
|
||||
// add bottom border
|
||||
@@ -269,11 +271,13 @@ public class PlateFieldFactory extends FieldFactory {
|
||||
return isClipped;
|
||||
}
|
||||
|
||||
private boolean addSideBorders(List<FieldElement> comments) {
|
||||
private boolean addSideBorders(List<FieldElement> comments, int rowOffset) {
|
||||
boolean isClipped = false;
|
||||
|
||||
for (int i = 0; i < comments.size(); i++) {
|
||||
FieldElementResult result = addSideBorder(comments.get(i), i, false);
|
||||
FieldElement element = comments.get(i);
|
||||
int row = rowOffset + i;
|
||||
FieldElementResult result = addSideBorder(element, row, false);
|
||||
isClipped |= result.isClipped();
|
||||
comments.set(i, result.getFieldElement());
|
||||
}
|
||||
@@ -293,13 +297,16 @@ public class PlateFieldFactory extends FieldFactory {
|
||||
int sideSpaceWidth = 2 * spaceWidth;
|
||||
int availableWidth = fullStarWidth - sideStarWidth - sideSpaceWidth;
|
||||
if (availableWidth < element.getStringWidth()) {
|
||||
// not enough room; clip the text and add ellipses
|
||||
// not enough room; clip the text and add ellipsis
|
||||
isClipped = true;
|
||||
ellipsisText = ELLIPSIS;
|
||||
ellipsisWidth = getMetrics().charWidth('.') * ELLIPSIS.length();
|
||||
availableWidth -= ellipsisWidth;
|
||||
int charsThatFit = element.getMaxCharactersForWidth(availableWidth);
|
||||
|
||||
FieldElement fullElement = element;
|
||||
element = element.substring(0, charsThatFit); // clip
|
||||
element = new ClippedPlateRowFieldElement(fullElement, element);
|
||||
}
|
||||
|
||||
int paddingWidth = sideStarWidth + sideSpaceWidth;
|
||||
@@ -313,24 +320,28 @@ public class PlateFieldFactory extends FieldFactory {
|
||||
buffy.append('*').append(' ');
|
||||
addPaddingSpaces(buffy, prePaddingCharCount);
|
||||
|
||||
FieldElement prefix = new TextFieldElement(
|
||||
new AttributedString(buffy.toString(), CommentColors.PLATE, getMetrics()), row, 0);
|
||||
AttributedString as = toAttrString(buffy.toString());
|
||||
FieldElement prefix = new TextFieldElement(as, row, 0);
|
||||
|
||||
FieldElement ellipsis = new TextFieldElement(
|
||||
new AttributedString(ellipsisText, CommentColors.PLATE, getMetrics()), row,
|
||||
prefix.length() + element.length());
|
||||
int col = prefix.length() + element.length();
|
||||
as = toAttrString(ellipsisText);
|
||||
FieldElement ellipsis = new TextFieldElement(as, row, col);
|
||||
|
||||
buffy.setLength(0);
|
||||
addPaddingSpaces(buffy, postPaddingCharCount);
|
||||
buffy.append(' ').append('*');
|
||||
|
||||
FieldElement suffix = new TextFieldElement(
|
||||
new AttributedString(buffy.toString(), CommentColors.PLATE, getMetrics()), row,
|
||||
prefix.length() + element.length() + ellipsis.length());
|
||||
col = col + ellipsis.length();
|
||||
as = toAttrString(buffy.toString());
|
||||
FieldElement suffix = new TextFieldElement(as, row, col);
|
||||
|
||||
return new FieldElementResult(
|
||||
new CompositeFieldElement(new FieldElement[] { prefix, element, ellipsis, suffix }),
|
||||
isClipped);
|
||||
CompositeFieldElement fullElement =
|
||||
new PlateCommentRowElement(new FieldElement[] { prefix, element, ellipsis, suffix });
|
||||
return new FieldElementResult(fullElement, isClipped);
|
||||
}
|
||||
|
||||
private AttributedString toAttrString(String s) {
|
||||
return new AttributedString(s, CommentColors.PLATE, getMetrics());
|
||||
}
|
||||
|
||||
private void addPaddingSpaces(StringBuilder buf, int count) {
|
||||
@@ -509,8 +520,10 @@ public class PlateFieldFactory extends FieldFactory {
|
||||
commentRow = -1; // clicked above the comment or the bottom decoration line
|
||||
}
|
||||
|
||||
return new PlateFieldLocation(cu.getProgram(), ((CodeUnit) proxyObject).getMinAddress(),
|
||||
cpath, commentRow, dataLocation.col(), comments, commentRow);
|
||||
Program p = cu.getProgram();
|
||||
Address addr = cu.getMinAddress();
|
||||
int charOffset = dataLocation.col();
|
||||
return new PlateFieldLocation(p, addr, cpath, commentRow, charOffset, comments, commentRow);
|
||||
}
|
||||
|
||||
private int getNumberOfLeadingFillerLines(ListingField listingField) {
|
||||
@@ -526,11 +539,10 @@ public class PlateFieldFactory extends FieldFactory {
|
||||
public FieldLocation getFieldLocation(ListingField listingField, BigInteger index, int fieldNum,
|
||||
ProgramLocation programLoc) {
|
||||
|
||||
if (!(programLoc instanceof CommentFieldLocation)) {
|
||||
if (!(programLoc instanceof CommentFieldLocation commentLocation)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
CommentFieldLocation commentLocation = (CommentFieldLocation) programLoc;
|
||||
if (commentLocation.getCommentType() != CommentType.PLATE) {
|
||||
return null;
|
||||
}
|
||||
@@ -570,15 +582,45 @@ public class PlateFieldFactory extends FieldFactory {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
This field adds decoration lines to the model lines. The 'data row' is the raw model
|
||||
data that is the comments plus the header blank lines and asteriscs.
|
||||
*/
|
||||
int commentRow = commentLocation.getRow();
|
||||
int numberBlankLines = getNumberBlankLines(cu, hasComment);
|
||||
int headerCount = hasComment ? 1 : 0;
|
||||
int dataRow = commentRow + numberBlankLines + headerCount;
|
||||
ListingTextField listingTf = (ListingTextField) listingField;
|
||||
int charOffset = commentLocation.getCharOffset();
|
||||
FieldLocation clippedLocation =
|
||||
getClippedFieldLocation(listingTf, index, fieldNum, dataRow, charOffset);
|
||||
if (clippedLocation != null) {
|
||||
return clippedLocation;
|
||||
}
|
||||
|
||||
ListingTextField listingTextField = (ListingTextField) listingField;
|
||||
RowColLocation location =
|
||||
listingTextField.dataToScreenLocation(dataRow, commentLocation.getCharOffset());
|
||||
RowColLocation location = listingTf.dataToScreenLocation(dataRow, charOffset);
|
||||
return new FieldLocation(index, fieldNum, location.row(), location.col());
|
||||
|
||||
}
|
||||
|
||||
private FieldLocation getClippedFieldLocation(ListingTextField listingTextField,
|
||||
BigInteger index, int fieldNum, int dataRow, int charOffset) {
|
||||
|
||||
PlateListingTextField pltf = (PlateListingTextField) listingTextField;
|
||||
PlateFieldTextField ptf = pltf.getPlateTextField();
|
||||
PlateCommentRowElement element = ptf.getPlateCommentElement(dataRow);
|
||||
if (element == null || !element.isClipped()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String clippedText = element.getClippedText();
|
||||
if (charOffset > clippedText.length()) {
|
||||
// place text at the end of the ellipsis
|
||||
int col = element.getEllipsisEnd();
|
||||
return new FieldLocation(index, fieldNum, dataRow, col);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -754,15 +796,27 @@ public class PlateFieldFactory extends FieldFactory {
|
||||
|
||||
private boolean isCommentClipped;
|
||||
private String commentText;
|
||||
private List<FieldElement> textElements;
|
||||
|
||||
PlateFieldTextField(List<FieldElement> textElements, PlateFieldFactory factory,
|
||||
ProxyObj<?> proxy, int startX, int width, String commentText,
|
||||
boolean isCommentClipped, FieldHighlightFactory hlFactory) {
|
||||
super(textElements, startX, width, Integer.MAX_VALUE, hlFactory);
|
||||
this.textElements = textElements;
|
||||
this.commentText = commentText;
|
||||
this.isCommentClipped = isCommentClipped;
|
||||
}
|
||||
|
||||
public PlateCommentRowElement getPlateCommentElement(int viewRow) {
|
||||
|
||||
FieldElement element = textElements.get(viewRow);
|
||||
if (!(element instanceof PlateCommentRowElement)) {
|
||||
return null; // this row is not a comment row
|
||||
}
|
||||
|
||||
return (PlateCommentRowElement) element;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isClipped() {
|
||||
return isCommentClipped;
|
||||
@@ -798,6 +852,66 @@ public class PlateFieldFactory extends FieldFactory {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A composite element for a row of text inside of the plate comment.
|
||||
*/
|
||||
private static class PlateCommentRowElement extends CompositeFieldElement {
|
||||
|
||||
public PlateCommentRowElement(FieldElement[] elements) {
|
||||
super(elements);
|
||||
}
|
||||
|
||||
int getEllipsisEnd() {
|
||||
int prefixCol = 0;
|
||||
int commentCol = 1;
|
||||
FieldElement prefix = getFieldElementByIndex(prefixCol);
|
||||
FieldElement comment = getFieldElementByIndex(commentCol);
|
||||
return prefix.length() + comment.length() + ELLIPSIS.length();
|
||||
}
|
||||
|
||||
String getClippedText() {
|
||||
int commentCol = 1;
|
||||
FieldElement comment = getFieldElementByIndex(commentCol);
|
||||
return comment.getText();
|
||||
}
|
||||
|
||||
boolean isClipped() {
|
||||
// Our structure when constructed of field elements:
|
||||
// prefix, comment, ellipsis, suffix
|
||||
// (where ellipsis will be empty when not clipped)
|
||||
|
||||
int commentCol = 1;
|
||||
FieldElement comment = getFieldElementByIndex(commentCol);
|
||||
return comment instanceof ClippedPlateRowFieldElement;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A class that represents clipped comment content. It has the clipped content and the full
|
||||
* content.
|
||||
*/
|
||||
private static class ClippedPlateRowFieldElement extends WrappedFieldElement {
|
||||
|
||||
private FieldElement fullElement;
|
||||
|
||||
protected ClippedPlateRowFieldElement(FieldElement fullElement, FieldElement delegate) {
|
||||
super(delegate);
|
||||
this.fullElement = fullElement;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FieldElement substring(int start, int end) {
|
||||
FieldElement newDelegate = super.substring(start, end);
|
||||
return new ClippedPlateRowFieldElement(fullElement, newDelegate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FieldElement replaceAll(char[] targets, char replacement) {
|
||||
FieldElement newDelegate = super.replaceAll(targets, replacement);
|
||||
return new ClippedPlateRowFieldElement(fullElement, newDelegate);
|
||||
}
|
||||
}
|
||||
|
||||
private class FieldElementResult {
|
||||
private FieldElement element;
|
||||
private boolean isClipped;
|
||||
|
||||
+6
-4
@@ -4,9 +4,9 @@
|
||||
* 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.
|
||||
@@ -120,8 +120,10 @@ abstract public class AbstractTextFieldElement implements FieldElement {
|
||||
|
||||
@Override
|
||||
public int getCharacterIndexForDataLocation(int dataRow, int dataColumn) {
|
||||
if (dataRow == row && (dataColumn >= column) && (dataColumn <= column + length())) {
|
||||
return dataColumn - column;
|
||||
int start = column;
|
||||
int end = start + length();
|
||||
if (dataRow == row && dataColumn >= start && dataColumn <= end) {
|
||||
return dataColumn - start;
|
||||
}
|
||||
|
||||
return -1;
|
||||
|
||||
+13
-22
@@ -4,9 +4,9 @@
|
||||
* 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.
|
||||
@@ -249,7 +249,6 @@ public class ClippingTextField implements TextField {
|
||||
}
|
||||
|
||||
void print(Graphics g, PaintContext context) {
|
||||
// TODO fix printing
|
||||
textElement.paint(null, g, startX, 0);
|
||||
if (isClipped) {
|
||||
paintDots(g, startX + textElement.getStringWidth());
|
||||
@@ -337,15 +336,6 @@ public class ClippingTextField implements TextField {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a single column value into a MultiStringLocation which specifies
|
||||
* a string index and a column position within that string.
|
||||
*
|
||||
* @param screenColumn
|
||||
* the overall column position in the total String.
|
||||
* @return MultiStringLocation the MultiStringLocation corresponding to the
|
||||
* given column.
|
||||
*/
|
||||
@Override
|
||||
public RowColLocation screenToDataLocation(int screenRow, int screenColumn) {
|
||||
return originalElement.getDataLocationForCharacterIndex(screenColumn);
|
||||
@@ -354,15 +344,19 @@ public class ClippingTextField implements TextField {
|
||||
@Override
|
||||
public RowColLocation dataToScreenLocation(int dataRow, int dataColumn) {
|
||||
|
||||
int column = textElement.getCharacterIndexForDataLocation(dataRow, dataColumn);
|
||||
int column = originalElement.getCharacterIndexForDataLocation(dataRow, dataColumn);
|
||||
if (column < 0) {
|
||||
column = textElement.getCharacterIndexForDataLocation(dataRow, dataColumn - 3);
|
||||
if (column < 0) {
|
||||
return new DefaultRowColLocation(0, textElement.length());
|
||||
}
|
||||
return new RowColLocation(0, textElement.length());
|
||||
return new DefaultRowColLocation(0, textElement.length());
|
||||
}
|
||||
return new RowColLocation(0, column);
|
||||
|
||||
// the text of this field does not contain the '...' when clipped, that is added when
|
||||
// rendered by the paint() method
|
||||
int screenLength = textElement.length();
|
||||
boolean locationClipped = column > screenLength;
|
||||
|
||||
// Note: the column value we return is 0, as the client will update the row when using
|
||||
// multiple clipping text field rows.
|
||||
return new RowColLocation(0, column, locationClipped);
|
||||
}
|
||||
|
||||
private int findX(int col) {
|
||||
@@ -372,9 +366,6 @@ public class ClippingTextField implements TextField {
|
||||
return textElement.substring(0, col).getStringWidth();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the text is clipped (truncated)
|
||||
*/
|
||||
@Override
|
||||
public boolean isClipped() {
|
||||
return isClipped;
|
||||
|
||||
+9
-9
@@ -4,9 +4,9 @@
|
||||
* 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.
|
||||
@@ -208,6 +208,10 @@ public class CompositeFieldElement implements FieldElement {
|
||||
return fieldElements.length;
|
||||
}
|
||||
|
||||
protected FieldElement getFieldElementByIndex(int index) {
|
||||
return fieldElements[index];
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
// Location Info
|
||||
//==================================================================================================
|
||||
@@ -220,18 +224,14 @@ public class CompositeFieldElement implements FieldElement {
|
||||
|
||||
@Override
|
||||
public int getCharacterIndexForDataLocation(int dataRow, int dataColumn) {
|
||||
|
||||
int columnsSoFar = 0;
|
||||
for (int i = fieldElements.length - 1; i >= 0; i--) {
|
||||
columnsSoFar += fieldElements[i].length();
|
||||
for (int i = 0; i < fieldElements.length; i++) {
|
||||
int column = fieldElements[i].getCharacterIndexForDataLocation(dataRow, dataColumn);
|
||||
if (column != -1) {
|
||||
// column value is relative to the current field; convert it to this field's offset
|
||||
int fieldStart = length() - columnsSoFar;
|
||||
return fieldStart + column;
|
||||
return columnsSoFar + column;
|
||||
}
|
||||
columnsSoFar += fieldElements[i].length();
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
+2
-7
@@ -4,9 +4,9 @@
|
||||
* 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.
|
||||
@@ -582,7 +582,6 @@ public class CompositeVerticalLayoutTextField implements TextField {
|
||||
private class FieldRow {
|
||||
private TextField field;
|
||||
private int displayRowOffset;
|
||||
private int yOffset;
|
||||
|
||||
FieldRow(TextField field, int rowOffset, int yOffset) {
|
||||
this.field = field;
|
||||
@@ -598,10 +597,6 @@ public class CompositeVerticalLayoutTextField implements TextField {
|
||||
return relativeRow + displayRowOffset;
|
||||
}
|
||||
|
||||
int getY() {
|
||||
return yOffset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return Json.toString(this);
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
* 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.
|
||||
@@ -192,7 +192,7 @@ public interface Field {
|
||||
public String getTextWithLineSeparators();
|
||||
|
||||
/**
|
||||
* Returns the row, column position for an offset into the string returned by getText()
|
||||
* Returns the row, column position for an offset into the string returned by getText()
|
||||
* @param textOffset the offset into the entire text string for this field
|
||||
* @return a RowColLocation that contains the row,column location in the field for a position in
|
||||
* the overall field text
|
||||
@@ -206,4 +206,12 @@ public interface Field {
|
||||
* @return the offset
|
||||
*/
|
||||
public int screenLocationToTextOffset(int row, int col);
|
||||
|
||||
/**
|
||||
* Returns true if the field is not displaying all the text information
|
||||
* @return true if the field is not displaying all the text information
|
||||
*/
|
||||
public default boolean isClipped() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
+2
-8
@@ -4,9 +4,9 @@
|
||||
* 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.
|
||||
@@ -43,12 +43,6 @@ public interface TextField extends Field {
|
||||
*/
|
||||
public RowColLocation dataToScreenLocation(int dataRow, int dataColumn);
|
||||
|
||||
/**
|
||||
* Returns true if the field is not displaying all the text information
|
||||
* @return true if the field is not displaying all the text information
|
||||
*/
|
||||
public boolean isClipped();
|
||||
|
||||
/**
|
||||
* Returns the FieldElement at the given screen location.
|
||||
* @param screenRow the row on the screen
|
||||
|
||||
+1
-1
@@ -482,7 +482,7 @@ public class VerticalLayoutTextField implements TextField {
|
||||
// A DefaultRowColLocation means that the line did not have an exact match for
|
||||
// the dataRow and dataColumn, so need to keep looking at each line.
|
||||
if (!(loc instanceof DefaultRowColLocation)) {
|
||||
return new RowColLocation(i, loc.col());
|
||||
return loc.withRow(i);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package docking.widgets.fieldpanel.field;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Graphics;
|
||||
|
||||
import javax.swing.JComponent;
|
||||
|
||||
import docking.widgets.fieldpanel.support.RowColLocation;
|
||||
|
||||
/**
|
||||
* A simple field element that wraps another field element. This class allows clients to wrap field
|
||||
* elements without having to re-implement all of the methods themselves.
|
||||
*/
|
||||
public abstract class WrappedFieldElement implements FieldElement {
|
||||
|
||||
private FieldElement delegate;
|
||||
|
||||
public WrappedFieldElement(FieldElement delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getText() {
|
||||
return delegate.getText();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int length() {
|
||||
return delegate.length();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getStringWidth() {
|
||||
return delegate.getStringWidth();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeightAbove() {
|
||||
return delegate.getHeightAbove();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeightBelow() {
|
||||
return delegate.getHeightBelow();
|
||||
}
|
||||
|
||||
@Override
|
||||
public char charAt(int index) {
|
||||
return delegate.charAt(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Color getColor(int charIndex) {
|
||||
return delegate.getColor(charIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FieldElement substring(int start) {
|
||||
return delegate.substring(start);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FieldElement substring(int start, int end) {
|
||||
return delegate.substring(start, end);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FieldElement replaceAll(char[] targets, char replacement) {
|
||||
return delegate.replaceAll(targets, replacement);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxCharactersForWidth(int width) {
|
||||
return delegate.getMaxCharactersForWidth(width);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RowColLocation getDataLocationForCharacterIndex(int characterIndex) {
|
||||
return delegate.getDataLocationForCharacterIndex(characterIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCharacterIndexForDataLocation(int dataRow, int dataColumn) {
|
||||
return delegate.getCharacterIndexForDataLocation(dataRow, dataColumn);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void paint(JComponent c, Graphics g, int x, int y) {
|
||||
delegate.paint(c, g, x, y);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FieldElement getFieldElement(int column) {
|
||||
return delegate.getFieldElement(column);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return delegate.toString();
|
||||
}
|
||||
}
|
||||
+31
-11
@@ -4,9 +4,9 @@
|
||||
* 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.
|
||||
@@ -15,12 +15,15 @@
|
||||
*/
|
||||
package docking.widgets.fieldpanel.support;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Simple class to return a row, column location.
|
||||
*/
|
||||
public class RowColLocation {
|
||||
protected int row;
|
||||
protected int col;
|
||||
private boolean isHidden;
|
||||
|
||||
/**
|
||||
* Constructs a new RowColLocation with the given row and column.
|
||||
@@ -30,6 +33,19 @@ public class RowColLocation {
|
||||
public RowColLocation(int row, int col) {
|
||||
this.row = row;
|
||||
this.col = col;
|
||||
this.isHidden = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new RowColLocation with the given row and column.
|
||||
* @param row the row location
|
||||
* @param col the column location
|
||||
* @param isHidden true if the given location is replaced with ellipsis in the UI
|
||||
*/
|
||||
public RowColLocation(int row, int col, boolean isHidden) {
|
||||
this.row = row;
|
||||
this.col = col;
|
||||
this.isHidden = isHidden;
|
||||
}
|
||||
|
||||
public int row() {
|
||||
@@ -40,26 +56,30 @@ public class RowColLocation {
|
||||
return col;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return true if this location is hidden, such as when the location is replaced by an ellipsis
|
||||
* in the UI}
|
||||
*/
|
||||
public boolean isHidden() {
|
||||
return isHidden;
|
||||
}
|
||||
|
||||
public RowColLocation withCol(int newColumn) {
|
||||
return new RowColLocation(row, newColumn);
|
||||
return new RowColLocation(row, newColumn, isHidden);
|
||||
}
|
||||
|
||||
public RowColLocation withRow(int newRow) {
|
||||
return new RowColLocation(newRow, col);
|
||||
return new RowColLocation(newRow, col, isHidden);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return row + "," + col;
|
||||
return row + "," + col + (isHidden ? ", hidden" : "");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + col;
|
||||
result = prime * result + row;
|
||||
return result;
|
||||
return Objects.hash(row, col, isHidden);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -73,6 +93,6 @@ public class RowColLocation {
|
||||
}
|
||||
|
||||
RowColLocation loc = (RowColLocation) object;
|
||||
return (row == loc.row) && (col == loc.col);
|
||||
return row == loc.row && col == loc.col && isHidden == loc.isHidden;
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -4,9 +4,9 @@
|
||||
* 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.
|
||||
@@ -114,7 +114,7 @@ public class FlowLayoutTextFieldTest extends AbstractGenericTest {
|
||||
assertEquals(new RowColLocation(1, 4), textField.dataToScreenLocation(2, 4));
|
||||
|
||||
// Supercalifra (12 chars); ... (3 chars); Supercalifra... (15 chars)
|
||||
assertEquals(new RowColLocation(1, 12), textField.dataToScreenLocation(2, 15));
|
||||
assertEquals(new RowColLocation(1, 15, true), textField.dataToScreenLocation(2, 15));
|
||||
|
||||
assertEquals(new RowColLocation(2, 0), textField.dataToScreenLocation(3, 0));
|
||||
assertEquals(new RowColLocation(2, 4), textField.dataToScreenLocation(3, 4));
|
||||
|
||||
+5
-3
@@ -4,9 +4,9 @@
|
||||
* 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.
|
||||
@@ -92,7 +92,9 @@ public class VerticalLayoutTextFieldTest extends AbstractGenericTest {
|
||||
assertEquals(new RowColLocation(2, 0), field.dataToScreenLocation(2, 0));
|
||||
assertEquals(new RowColLocation(2, 4), field.dataToScreenLocation(2, 4));
|
||||
assertEquals(new RowColLocation(2, 12), field.dataToScreenLocation(2, 12));
|
||||
assertEquals(new RowColLocation(2, 12), field.dataToScreenLocation(2, 15));
|
||||
|
||||
// clipped text reports as hidden
|
||||
assertEquals(new RowColLocation(2, 15, true), field.dataToScreenLocation(2, 15));
|
||||
|
||||
assertEquals(new RowColLocation(3, 0), field.dataToScreenLocation(3, 0));
|
||||
assertEquals(new RowColLocation(3, 4), field.dataToScreenLocation(3, 4));
|
||||
|
||||
+12
-3
@@ -4,9 +4,9 @@
|
||||
* 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.
|
||||
@@ -162,7 +162,8 @@ public class CompositeVerticalLayoutTextFieldTest extends AbstractGenericTest {
|
||||
assertRowCol(2, 0, field.dataToScreenLocation(2, 0));
|
||||
assertRowCol(2, 4, field.dataToScreenLocation(2, 4));
|
||||
assertRowCol(2, 12, field.dataToScreenLocation(2, 12));
|
||||
assertRowCol(2, 12, field.dataToScreenLocation(2, 15));
|
||||
|
||||
assertRowColClipped(2, 15, field.dataToScreenLocation(2, 15));
|
||||
|
||||
assertRowCol(3, 0, field.dataToScreenLocation(3, 0));
|
||||
assertRowCol(3, 4, field.dataToScreenLocation(3, 4));
|
||||
@@ -584,4 +585,12 @@ public class CompositeVerticalLayoutTextFieldTest extends AbstractGenericTest {
|
||||
assertEquals("Wrong row", expectedRow, actualLocation.row());
|
||||
assertEquals("Wrong column", expectedColumn, actualLocation.col());
|
||||
}
|
||||
|
||||
private void assertRowColClipped(int expectedRow, int expectedColumn,
|
||||
RowColLocation actualLocation) {
|
||||
assertEquals("Wrong row", expectedRow, actualLocation.row());
|
||||
assertEquals("Wrong column", expectedColumn, actualLocation.col());
|
||||
assertTrue("Text should have been clipped", actualLocation.isHidden());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user