Skip to content

Commit

Permalink
Add quick search to Edit view of structured resources
Browse files Browse the repository at this point in the history
Typing characters while a table cell of the "Attribute" or "Offset"
column is selected jumps to the first matching entry relative to the
current table row, wrapping around if needed.

For quick-searching relative offsets in substructures, start with a plus
sign (+). Requires relative offsets to be enabled.
  • Loading branch information
Argent77 committed Apr 19, 2024
1 parent ac0edbb commit cd05ff2
Showing 1 changed file with 168 additions and 0 deletions.
168 changes: 168 additions & 0 deletions src/org/infinity/gui/StructViewer.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
import java.awt.event.ComponentListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.print.PageFormat;
Expand All @@ -30,6 +32,7 @@
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
Expand All @@ -50,6 +53,7 @@
import javax.swing.ListSelectionModel;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.ListSelectionEvent;
Expand Down Expand Up @@ -298,6 +302,7 @@ public Component getTableCellRendererComponent(JTable table, Object value, boole
popupmenu.add(miShowNewViewer);
}
table.addMouseListener(new PopupListener());
table.addKeyListener(new TableKeyListener());
miCopyValue.setEnabled(false);
miPasteValue.setEnabled(false);
miCut.setEnabled(false);
Expand Down Expand Up @@ -1538,4 +1543,167 @@ public int print(Graphics graphics, PageFormat pageFormat, int pageIndex) throws
return Printable.PAGE_EXISTS;
}
}

/**
* This class implements a quick search feature that jumps to the next table entry matching entered characters.
*/
private class TableKeyListener extends KeyAdapter {
private static final int TIMER_DELAY = 1000;

private final Timer timer;
private String currentKey;

public TableKeyListener() {
this.currentKey = "";
this.timer = new Timer(TIMER_DELAY, e -> resetKey());
this.timer.setRepeats(false);
}

public void keyTyped(KeyEvent event) {
if (KeyEvent.VK_BACK_SPACE == event.getKeyCode()) {
removeKeyChar();
} else if (KeyEvent.VK_ENTER != event.getKeyCode()) {
appendKeyChar(event.getKeyChar());
}
timerReset();
selectEntry(table.getSelectedColumn(), table.getSelectedRow(), currentKey);
}

/** Adds the specified character to the search string. */
private void appendKeyChar(char ch) {
synchronized (timer) {
// skip control characters
if (ch >= ' ') {
currentKey += Character.toString(ch);
}
}
}

/** Removes the last charcater from the search string. */
private void removeKeyChar() {
synchronized (timer) {
if (currentKey.length() > 0) {
currentKey = currentKey.substring(0, currentKey.length() - 1);
}
}
}

/** Clears the search string. */
private void resetKey() {
synchronized (timer) {
currentKey = "";
}
}

/** Restarts the input timer. */
private void timerReset() {
if (timer.isRunning()) {
timer.restart();
} else {
timer.start();
}
}

/** Returns {@code true} only if the specified column points to the "Attribute" column. */
private boolean isAttributeColumn(int col) {
if (col >= 0) {
final String colName = table.getModel().getColumnName(col);
return AbstractStruct.COLUMN_ATTRIBUTE.equals(colName);
}
return false;
}

/** Returns {@code true} only if the specified column points to the "Offset" column. */
private boolean isOffsetColumn(int col) {
if (col >= 0) {
final String colName = table.getModel().getColumnName(col);
return AbstractStruct.COLUMN_OFFSET.equals(colName);
}
return false;
}

/** Selects the next matching row based on the given arguments. */
private void selectEntry(int col, int row, String key) {
row = Math.max(-1, row);
col = Math.max(0, col);

// only enabled for "Attribute" and "Offset" columns
if (!isAttributeColumn(col) && !isOffsetColumn(col)) {
return;
}

if (key == null) {
key = "";
}

final String curKey;
final String regex, replacement;
if (isAttributeColumn(col)) {
// "Attribute" column
curKey = key.replaceFirst("^ +", "");
regex = null;
replacement = null;
} else {
// "Offset" column
if (key.startsWith("+")) {
final String ofsPattern = "[-0-9a-fA-F]+ h";
regex = ofsPattern + " \\((" + ofsPattern + ")\\)";
replacement = "$1";
} else {
regex = null;
replacement = null;
}
curKey = key.replaceAll("[^0-9a-fA-F]+", "");
}

if (curKey.isEmpty()) {
return;
}

for (int y = row, count = table.getRowCount(); y < count; y++) {
if (findMatch(y, col, curKey, regex, replacement)) {
table.getSelectionModel().setSelectionInterval(y, y);
return;
}
}

// wrap around
for (int y = 0; y < row; y++) {
if (findMatch(y, col, curKey, regex, replacement)) {
table.getSelectionModel().setSelectionInterval(y, y);
return;
}
}
}

/**
* Returns whether the specified key string matches the cell content at the given table location.
*
* @param row Row index of the cell.
* @param col Column index of the cell.
* @param key The search string.
* @param regex An optional regular expression that is applied to the cell content. Specify {@code null} to
* ignore.
* @param replacement An optional replacement string that is used in conjunction with {@code regex}. Specify
* {@code null} to ignore.
* @return {@code true} if a match is found, {@code false} otherwise.
*/
private boolean findMatch(int row, int col, String key, String regex, String replacement) {
boolean retVal = false;
if (key != null) {
final Object o = table.getModel().getValueAt(row, col);
if (o != null) {
final String curKey = key.toUpperCase(Locale.ROOT);
final String cellText;
if (regex != null && replacement != null) {
cellText = o.toString().replaceFirst(regex, replacement).toUpperCase(Locale.ROOT);
} else {
cellText = o.toString().toUpperCase(Locale.ROOT);
}
retVal = cellText.startsWith(curKey);
}
}
return retVal;
}
}
}

0 comments on commit cd05ff2

Please sign in to comment.