mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-05-10 13:10:39 +08:00
Merge remote-tracking branch 'origin/GP-1-dragonmcher-find-results-table-context-sorting'
This commit is contained in:
+12
-90
@@ -15,17 +15,14 @@
|
||||
*/
|
||||
package ghidra.app.plugin.core.navigation.locationreferences;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.Component;
|
||||
import java.util.*;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.text.View;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import docking.widgets.search.SearchLocationContext;
|
||||
import docking.widgets.search.SearchLocationContextRenderer;
|
||||
import docking.widgets.table.GTableCellRenderingData;
|
||||
import generic.theme.GThemeDefaults.Colors;
|
||||
import ghidra.docking.settings.Settings;
|
||||
import ghidra.framework.plugintool.ServiceProvider;
|
||||
import ghidra.program.model.address.Address;
|
||||
@@ -33,7 +30,6 @@ import ghidra.program.model.listing.Program;
|
||||
import ghidra.program.util.ProgramLocation;
|
||||
import ghidra.util.datastruct.Accumulator;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.layout.AbstractLayoutManager;
|
||||
import ghidra.util.table.AddressBasedTableModel;
|
||||
import ghidra.util.table.AddressPreviewTableModel;
|
||||
import ghidra.util.table.column.AbstractGhidraColumnRenderer;
|
||||
@@ -237,8 +233,14 @@ class LocationReferencesTableModel extends AddressBasedTableModel<LocationRefere
|
||||
|
||||
private class ContextCellRenderer extends AbstractGhidraColumnRenderer<LocationReference> {
|
||||
|
||||
private JPanel htmlContainer = new JPanel(new HtmlTruncatingLayout());
|
||||
private JLabel ellipsisLabel = new JLabel("...");
|
||||
private SearchLocationContextRenderer contextRenderer =
|
||||
new SearchLocationContextRenderer() {
|
||||
@Override
|
||||
protected SearchLocationContext getContext(GTableCellRenderingData d) {
|
||||
LocationReference lr = (LocationReference) d.getRowObject();
|
||||
return lr.getContext();
|
||||
}
|
||||
};
|
||||
|
||||
ContextCellRenderer() {
|
||||
setHTMLRenderingEnabled(true);
|
||||
@@ -247,9 +249,6 @@ class LocationReferencesTableModel extends AddressBasedTableModel<LocationRefere
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
|
||||
|
||||
// initialize
|
||||
super.getTableCellRendererComponent(data);
|
||||
|
||||
LocationReference rowObject = (LocationReference) data.getRowObject();
|
||||
Callback offcutCallback = () -> {
|
||||
boolean isSelected = data.isSelected();
|
||||
@@ -257,29 +256,13 @@ class LocationReferencesTableModel extends AddressBasedTableModel<LocationRefere
|
||||
};
|
||||
String refTypeString = getRefTypeString(rowObject, offcutCallback);
|
||||
if (refTypeString != null) {
|
||||
super.getTableCellRendererComponent(data);
|
||||
setText(refTypeString);
|
||||
return this;
|
||||
}
|
||||
|
||||
/*
|
||||
At this point we have html context. Build a renderer that is a panel with 2
|
||||
children: the html label (this renderer object) and an ellipsis label that will
|
||||
be visible as needed.
|
||||
*/
|
||||
SearchLocationContext context = rowObject.getContext();
|
||||
String html = context.getBoldMatchingText();
|
||||
setText(html);
|
||||
|
||||
ellipsisLabel.setOpaque(true);
|
||||
ellipsisLabel.setForeground(Colors.FOREGROUND);
|
||||
ellipsisLabel.setBackground(getBackground());
|
||||
|
||||
htmlContainer.setBackground(getBackground());
|
||||
htmlContainer.removeAll();
|
||||
htmlContainer.add(this);
|
||||
htmlContainer.add(ellipsisLabel);
|
||||
|
||||
return htmlContainer;
|
||||
return contextRenderer.renderHtmlContext(data, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -294,67 +277,6 @@ class LocationReferencesTableModel extends AddressBasedTableModel<LocationRefere
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A layout manager that positions 2 labels: a leading label with html and a trailing label
|
||||
* with an ellipsis, which may not be visible. JLabels rendering html will not show an
|
||||
* ellipsis when clipped. We use these 2 labels here to show when the leading html label's
|
||||
* text is clipped.
|
||||
*/
|
||||
private class HtmlTruncatingLayout extends AbstractLayoutManager {
|
||||
|
||||
@Override
|
||||
public Dimension preferredLayoutSize(Container parent) {
|
||||
|
||||
Dimension d = new Dimension();
|
||||
int n = parent.getComponentCount();
|
||||
for (int i = 0; i < n; i++) {
|
||||
Component c = parent.getComponent(i);
|
||||
Dimension cd = c.getPreferredSize();
|
||||
d.width += cd.width;
|
||||
d.height = Math.max(d.height, cd.height);
|
||||
}
|
||||
|
||||
Insets insets = parent.getInsets();
|
||||
d.width += insets.left + insets.right;
|
||||
d.height += insets.top + insets.bottom;
|
||||
return d;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void layoutContainer(Container parent) {
|
||||
// Assumption: the leading component is an html view; the trailing component is a
|
||||
// label with an ellipsis
|
||||
|
||||
JComponent c1 = (JComponent) parent.getComponent(0);
|
||||
Dimension d = parent.getSize();
|
||||
Insets insets = parent.getInsets();
|
||||
int width = d.width - insets.left - insets.right;
|
||||
|
||||
View v = (View) c1.getClientProperty("html");
|
||||
Insets i = c1.getInsets();
|
||||
int availableWidth = width - (i.left + i.right);
|
||||
int htmlw = (int) v.getPreferredSpan(View.X_AXIS);
|
||||
|
||||
JLabel c2 = (JLabel) parent.getComponent(1);
|
||||
Dimension c2d = c2.getPreferredSize();
|
||||
boolean isClipped = htmlw > availableWidth && width != 0;
|
||||
if (isClipped) {
|
||||
availableWidth -= c2d.width; // save room for ellipsis
|
||||
int c2x = availableWidth;
|
||||
int c2y = insets.top;
|
||||
c2.setBounds(c2x, c2y, c2d.width, c2d.height);
|
||||
}
|
||||
|
||||
c2.setVisible(isClipped);
|
||||
|
||||
int c1x = insets.left;
|
||||
int c1y = insets.top;
|
||||
int cyh = d.height - (i.top + i.bottom);
|
||||
c1.setBounds(c1x, c1y, availableWidth, cyh);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+3
-2
@@ -244,14 +244,14 @@ public class DecompilerSearcher implements FindDialogSearcher {
|
||||
FieldLocation fieldLocation =
|
||||
new FieldLocation(i, lineInfo.fieldNumber(), lineInfo.row(), lineInfo.column());
|
||||
int lineNumber = lineInfo.lineNumber();
|
||||
SearchLocationContext context = createContext(fullLine, match);
|
||||
SearchLocationContext context = createContext(fullLine, lineNumber, match);
|
||||
return new DecompilerSearchLocation(fieldLocation, match.start, match.end - 1,
|
||||
searchString, true, field.getText(), lineNumber, context);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private SearchLocationContext createContext(String line, SearchMatch match) {
|
||||
private SearchLocationContext createContext(String line, int lineNumber, SearchMatch match) {
|
||||
SearchLocationContextBuilder builder = new SearchLocationContextBuilder();
|
||||
int start = match.start;
|
||||
int end = match.end;
|
||||
@@ -261,6 +261,7 @@ public class DecompilerSearcher implements FindDialogSearcher {
|
||||
builder.append(line.substring(end));
|
||||
}
|
||||
|
||||
builder.lineNumber(lineNumber);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
|
||||
+4
-2
@@ -246,15 +246,16 @@ public class DecompilerTextFinder {
|
||||
TextLine firstLine = lineMatches.get(0);
|
||||
int lineNumber = firstLine.getLineNumber();
|
||||
AddressSet addresses = getAddresses(function, firstLine.getCLine());
|
||||
SearchLocationContext context = createMatchContext(lineMatches);
|
||||
SearchLocationContext context = createMatchContext(lineMatches, lineNumber);
|
||||
TextMatch match =
|
||||
new TextMatch(function, addresses, lineNumber, searchText, context, true);
|
||||
callback.accept(match);
|
||||
}
|
||||
|
||||
private SearchLocationContext createMatchContext(List<TextLine> matches) {
|
||||
private SearchLocationContext createMatchContext(List<TextLine> matches, int lineNumber) {
|
||||
|
||||
SearchLocationContextBuilder builder = new SearchLocationContextBuilder();
|
||||
builder.lineNumber(lineNumber);
|
||||
for (TextLine line : matches) {
|
||||
if (!builder.isEmpty()) {
|
||||
builder.newline();
|
||||
@@ -280,6 +281,7 @@ public class DecompilerTextFinder {
|
||||
}
|
||||
|
||||
SearchLocationContextBuilder builder = new SearchLocationContextBuilder();
|
||||
builder.lineNumber(line.getLineNumber());
|
||||
|
||||
int start = matcher.start();
|
||||
int end = matcher.end();
|
||||
|
||||
+9
-19
@@ -21,6 +21,7 @@ import java.util.function.Consumer;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import docking.widgets.search.SearchLocationContext;
|
||||
import docking.widgets.search.SearchLocationContextRenderer;
|
||||
import docking.widgets.table.*;
|
||||
import ghidra.docking.settings.Settings;
|
||||
import ghidra.framework.plugintool.ServiceProvider;
|
||||
@@ -30,7 +31,6 @@ import ghidra.program.model.listing.*;
|
||||
import ghidra.util.datastruct.Accumulator;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.table.GhidraProgramTableModel;
|
||||
import ghidra.util.table.column.AbstractGhidraColumnRenderer;
|
||||
import ghidra.util.table.column.GColumnRenderer;
|
||||
import ghidra.util.table.field.AbstractProgramBasedDynamicTableColumn;
|
||||
import ghidra.util.table.field.FunctionNameTableColumn;
|
||||
@@ -164,37 +164,27 @@ public class DecompilerTextFinderTableModel extends GhidraProgramTableModel<Text
|
||||
}
|
||||
|
||||
private class ContextCellRenderer
|
||||
extends AbstractGhidraColumnRenderer<SearchLocationContext> {
|
||||
extends SearchLocationContextRenderer {
|
||||
|
||||
{
|
||||
// the context uses html
|
||||
setHTMLRenderingEnabled(true);
|
||||
@Override
|
||||
protected SearchLocationContext getContext(GTableCellRenderingData d) {
|
||||
TextMatch m = (TextMatch) d.getRowObject();
|
||||
return m.getContext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
|
||||
|
||||
// initialize
|
||||
super.getTableCellRendererComponent(data);
|
||||
|
||||
TextMatch match = (TextMatch) data.getRowObject();
|
||||
SearchLocationContext context = match.getContext();
|
||||
String text;
|
||||
if (match.isMultiLine()) {
|
||||
// multi-line matches create visual noise when showing colors, as of much of the
|
||||
// entire line matches
|
||||
text = context.getPlainText();
|
||||
return renderPlainContext(data, context);
|
||||
}
|
||||
else {
|
||||
text = context.getBoldMatchingText();
|
||||
}
|
||||
setText(text);
|
||||
return this;
|
||||
|
||||
return renderHtmlContext(data, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFilterString(SearchLocationContext context, Settings settings) {
|
||||
return context.getPlainText();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+9
-35
@@ -16,7 +16,6 @@
|
||||
package docking.widgets;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Component;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.util.ArrayList;
|
||||
@@ -30,14 +29,12 @@ import javax.swing.table.TableModel;
|
||||
import docking.ComponentProvider;
|
||||
import docking.Tool;
|
||||
import docking.action.DockingAction;
|
||||
import docking.widgets.search.SearchLocationContext;
|
||||
import docking.widgets.search.SearchResults;
|
||||
import docking.widgets.search.*;
|
||||
import docking.widgets.table.*;
|
||||
import docking.widgets.table.actions.DeleteTableRowAction;
|
||||
import ghidra.docking.settings.Settings;
|
||||
import ghidra.framework.plugintool.ServiceProvider;
|
||||
import ghidra.framework.plugintool.ServiceProviderStub;
|
||||
import ghidra.util.table.column.AbstractGColumnRenderer;
|
||||
import ghidra.util.table.column.GColumnRenderer;
|
||||
|
||||
public class FindDialogResultsProvider extends ComponentProvider
|
||||
@@ -290,13 +287,17 @@ public class FindDialogResultsProvider extends ComponentProvider
|
||||
private class ContextColumn extends
|
||||
AbstractDynamicTableColumnStub<SearchLocation, SearchLocationContext> {
|
||||
|
||||
private GColumnRenderer<SearchLocationContext> renderer = new ContextCellRenderer();
|
||||
private SearchLocationContextRenderer renderer = new SearchLocationContextRenderer() {
|
||||
@Override
|
||||
protected SearchLocationContext getContext(GTableCellRenderingData d) {
|
||||
SearchLocation s = (SearchLocation) d.getRowObject();
|
||||
return s.getContext();
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public SearchLocationContext getValue(SearchLocation rowObject,
|
||||
Settings settings,
|
||||
ServiceProvider sp) throws IllegalArgumentException {
|
||||
|
||||
Settings settings, ServiceProvider sp) throws IllegalArgumentException {
|
||||
SearchLocationContext context = rowObject.getContext();
|
||||
return context;
|
||||
}
|
||||
@@ -310,33 +311,6 @@ public class FindDialogResultsProvider extends ComponentProvider
|
||||
public GColumnRenderer<SearchLocationContext> getColumnRenderer() {
|
||||
return renderer;
|
||||
}
|
||||
|
||||
private class ContextCellRenderer
|
||||
extends AbstractGColumnRenderer<SearchLocationContext> {
|
||||
|
||||
{
|
||||
// the context uses html
|
||||
setHTMLRenderingEnabled(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(GTableCellRenderingData cellData) {
|
||||
|
||||
// initialize
|
||||
super.getTableCellRendererComponent(cellData);
|
||||
|
||||
SearchLocation match = (SearchLocation) cellData.getRowObject();
|
||||
SearchLocationContext context = match.getContext();
|
||||
String text = context.getBoldMatchingText();
|
||||
setText(text);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFilterString(SearchLocationContext context, Settings settings) {
|
||||
return context.getPlainText();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+107
-5
@@ -26,7 +26,7 @@ import ghidra.util.HTMLUtilities;
|
||||
*
|
||||
* @see SearchLocationContextBuilder
|
||||
*/
|
||||
public class SearchLocationContext {
|
||||
public class SearchLocationContext implements Comparable<SearchLocationContext> {
|
||||
|
||||
private static final String EMBOLDEN_START =
|
||||
"<span style=\"background-color: #a3e4d7; color: black;\"><b><font size=4>";
|
||||
@@ -88,11 +88,27 @@ public class SearchLocationContext {
|
||||
}
|
||||
|
||||
/**
|
||||
* The full plain text of this context.
|
||||
* The full plain text of this context. Any non-negative line number will be prepended to the
|
||||
* text.
|
||||
* @return the text
|
||||
*/
|
||||
public String getPlainText() {
|
||||
String lnText = getLineNumberText(false);
|
||||
return getPlainText(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the plain text of this context, without html markup.
|
||||
*
|
||||
* @param includeLineNumber if true, any non-negative line number will be prepended to the text.
|
||||
* @return the text
|
||||
*/
|
||||
public String getPlainText(boolean includeLineNumber) {
|
||||
|
||||
String lnText = "";
|
||||
if (includeLineNumber) {
|
||||
lnText = getLineNumberText(false);
|
||||
}
|
||||
|
||||
StringBuilder buffy = new StringBuilder(lnText);
|
||||
for (Part part : parts) {
|
||||
buffy.append(part.getText());
|
||||
@@ -124,8 +140,8 @@ public class SearchLocationContext {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns HTML text for this context. Any matching items embedded in the returned string will
|
||||
* be bold.
|
||||
* Returns HTML text for this context. Any matching items embedded in the returned string will
|
||||
* be bold. Any non-negative line number will be prepended to the text.
|
||||
* @return the text
|
||||
*/
|
||||
public String getBoldMatchingText() {
|
||||
@@ -137,6 +153,27 @@ public class SearchLocationContext {
|
||||
return HTMLUtilities.HTML + buffy.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns HTML text for this context. Any matching items embedded in the returned string will
|
||||
* be bold.
|
||||
*
|
||||
* @param includeLineNumber if true, any non-negative line number will be prepended to the text.
|
||||
* @return the text
|
||||
*/
|
||||
public String getBoldMatchingText(boolean includeLineNumber) {
|
||||
|
||||
String lnText = "";
|
||||
if (includeLineNumber) {
|
||||
lnText = getLineNumberText(false);
|
||||
}
|
||||
|
||||
StringBuilder buffy = new StringBuilder(lnText);
|
||||
for (Part part : parts) {
|
||||
buffy.append(part.getHtmlText());
|
||||
}
|
||||
return HTMLUtilities.HTML + buffy.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns any sub-strings of this context's overall text that match client-defined input
|
||||
*
|
||||
@@ -166,10 +203,55 @@ public class SearchLocationContext {
|
||||
return getPlainText();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(SearchLocationContext other) {
|
||||
|
||||
// Use line numbers when both clients have them, as string integer comparisons do not
|
||||
// naturally sort by integer value.
|
||||
int l1 = getLineNumber();
|
||||
int l2 = other.getLineNumber();
|
||||
int result = Integer.compare(l1, l2);
|
||||
if (result != 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// Note: the debug text will call out the portion of the line that matches. For
|
||||
// multiple matches on the same line, we will have multiple rows. In that case,
|
||||
// we need the match markup to help sort those lines.
|
||||
String t1 = getDebugText();
|
||||
String t2 = other.getDebugText();
|
||||
return t1.compareTo(t2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(lineNumber, parts);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
SearchLocationContext other = (SearchLocationContext) obj;
|
||||
return lineNumber == other.lineNumber && Objects.equals(parts, other.parts);
|
||||
}
|
||||
|
||||
//=================================================================================================
|
||||
// Inner Classes
|
||||
//=================================================================================================
|
||||
|
||||
/**
|
||||
* A class that represents one or more characters within the full text of this context class
|
||||
*/
|
||||
static abstract class Part {
|
||||
|
||||
protected String text;
|
||||
|
||||
Part(String text) {
|
||||
@@ -189,6 +271,26 @@ public class SearchLocationContext {
|
||||
return updated;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(text);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
Part other = (Part) obj;
|
||||
return Objects.equals(text, other.text);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return Json.toString(this);
|
||||
|
||||
+156
@@ -0,0 +1,156 @@
|
||||
/* ###
|
||||
* 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.search;
|
||||
|
||||
import java.awt.*;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.text.View;
|
||||
|
||||
import docking.widgets.table.GTableCellRenderingData;
|
||||
import generic.theme.GThemeDefaults.Colors;
|
||||
import ghidra.docking.settings.Settings;
|
||||
import ghidra.util.layout.AbstractLayoutManager;
|
||||
import ghidra.util.table.column.AbstractGColumnRenderer;
|
||||
|
||||
/**
|
||||
* A renderer for {@link SearchLocationContext}. This renderer handles the complexity of rendering
|
||||
* html text with clipping.
|
||||
*/
|
||||
public abstract class SearchLocationContextRenderer
|
||||
extends AbstractGColumnRenderer<SearchLocationContext> {
|
||||
|
||||
private JPanel htmlContainer = new JPanel(new HtmlTruncatingLayout());
|
||||
private JLabel ellipsisLabel = new JLabel("...");
|
||||
|
||||
public SearchLocationContextRenderer() {
|
||||
setHTMLRenderingEnabled(true);
|
||||
}
|
||||
|
||||
protected abstract SearchLocationContext getContext(GTableCellRenderingData data);
|
||||
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
|
||||
|
||||
SearchLocationContext context = getContext(data);
|
||||
return renderHtmlContext(data, context);
|
||||
}
|
||||
|
||||
public Component renderPlainContext(GTableCellRenderingData data,
|
||||
SearchLocationContext context) {
|
||||
|
||||
super.getTableCellRendererComponent(data);
|
||||
|
||||
// Note: we do not include the line number prefix on the text, based on the assumption that
|
||||
// clients of this renderer will have a separate line number column.
|
||||
String text = context.getPlainText(false);
|
||||
setText(text);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Component renderHtmlContext(GTableCellRenderingData data,
|
||||
SearchLocationContext context) {
|
||||
|
||||
// initialize
|
||||
super.getTableCellRendererComponent(data);
|
||||
|
||||
/*
|
||||
We have html context. Build a renderer that is a panel with 2 children: the html label
|
||||
(this renderer object) and an ellipsis label that will be visible as needed.
|
||||
*/
|
||||
|
||||
// Note: we do not include the line number prefix on the text, based on the assumption that
|
||||
// clients of this renderer will have a separate line number column.
|
||||
String html = context.getBoldMatchingText(false);
|
||||
setText(html);
|
||||
|
||||
ellipsisLabel.setOpaque(true);
|
||||
ellipsisLabel.setForeground(Colors.FOREGROUND);
|
||||
ellipsisLabel.setBackground(getBackground());
|
||||
|
||||
htmlContainer.setBackground(getBackground());
|
||||
htmlContainer.removeAll();
|
||||
htmlContainer.add(this);
|
||||
htmlContainer.add(ellipsisLabel);
|
||||
|
||||
return htmlContainer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFilterString(SearchLocationContext rowObject, Settings settings) {
|
||||
return rowObject.getPlainText();
|
||||
}
|
||||
|
||||
/**
|
||||
* A layout manager that positions 2 labels: a leading label with html and a trailing label
|
||||
* with an ellipsis, which may not be visible. JLabels rendering html will not show an
|
||||
* ellipsis when clipped. We use these 2 labels here to show when the leading html label's
|
||||
* text is clipped.
|
||||
*/
|
||||
private class HtmlTruncatingLayout extends AbstractLayoutManager {
|
||||
|
||||
@Override
|
||||
public Dimension preferredLayoutSize(Container parent) {
|
||||
|
||||
Dimension d = new Dimension();
|
||||
int n = parent.getComponentCount();
|
||||
for (int i = 0; i < n; i++) {
|
||||
Component c = parent.getComponent(i);
|
||||
Dimension cd = c.getPreferredSize();
|
||||
d.width += cd.width;
|
||||
d.height = Math.max(d.height, cd.height);
|
||||
}
|
||||
|
||||
Insets insets = parent.getInsets();
|
||||
d.width += insets.left + insets.right;
|
||||
d.height += insets.top + insets.bottom;
|
||||
return d;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void layoutContainer(Container parent) {
|
||||
// Assumption: the leading component is an html view; the trailing component is a
|
||||
// label with an ellipsis
|
||||
|
||||
JComponent c1 = (JComponent) parent.getComponent(0);
|
||||
Dimension d = parent.getSize();
|
||||
Insets insets = parent.getInsets();
|
||||
int width = d.width - insets.left - insets.right;
|
||||
|
||||
View v = (View) c1.getClientProperty("html");
|
||||
Insets i = c1.getInsets();
|
||||
int availableWidth = width - (i.left + i.right);
|
||||
int htmlw = (int) v.getPreferredSpan(View.X_AXIS);
|
||||
|
||||
JLabel c2 = (JLabel) parent.getComponent(1);
|
||||
Dimension c2d = c2.getPreferredSize();
|
||||
boolean isClipped = htmlw > availableWidth && width != 0;
|
||||
if (isClipped) {
|
||||
availableWidth -= c2d.width; // save room for ellipsis
|
||||
int c2x = availableWidth;
|
||||
int c2y = insets.top;
|
||||
c2.setBounds(c2x, c2y, c2d.width, c2d.height);
|
||||
}
|
||||
|
||||
c2.setVisible(isClipped);
|
||||
|
||||
int c1x = insets.left;
|
||||
int c1y = insets.top;
|
||||
int cyh = d.height - (i.top + i.bottom);
|
||||
c1.setBounds(c1x, c1y, availableWidth, cyh);
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
@@ -270,6 +270,7 @@ public class TextComponentSearcher implements FindDialogSearcher {
|
||||
|
||||
private SearchLocationContext createContext(Line line, int start, int end) {
|
||||
SearchLocationContextBuilder builder = new SearchLocationContextBuilder();
|
||||
builder.lineNumber(line.lineNumber);
|
||||
String text = line.text();
|
||||
int offset = line.offset(); // document offset
|
||||
int rstart = start - offset; // line-relative start
|
||||
|
||||
Reference in New Issue
Block a user