// Near Infinity - An Infinity Engine Browser and Editor
// Copyright (C) 2001 - 2005 Jon Olav Hauglid
// See LICENSE.txt for license information
package org.infinity.gui.hexview;
import java.awt.Dialog;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.List;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.SwingConstants;
import javax.swing.WindowConstants;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import org.infinity.gui.ViewerUtil;
/**
* Searches for either text or byte values in the current resource data.
*/
public class FindDataDialog extends JDialog implements ActionListener, ItemListener, DocumentListener
{
/** The data type of the search string. */
public enum Type { TEXT, BYTES }
private String text;
private byte[] bytes;
private boolean retVal;
private JCheckBox cbCaseSensitive;
private JButton bOk, bCancel;
private JTextField tfSearch;
private JComboBox<String> cbType;
public FindDataDialog(Window parent)
{
super(parent, "Find", Dialog.ModalityType.APPLICATION_MODAL);
init(parent);
text = "";
bytes = new byte[0];
retVal = false;
}
//--------------------- Begin Interface ActionListener ---------------------
@Override
public void actionPerformed(ActionEvent e)
{
if (e.getSource() == bOk || e.getSource() == bCancel) {
retVal = (e.getSource() == bOk);
if(retVal) {
text = tfSearch.getText();
bytes = parseBytes(getText());
}
setVisible(false);
}
}
//--------------------- End Interface ActionListener ---------------------
//--------------------- Begin Interface ItemListener ---------------------
@Override
public void itemStateChanged(ItemEvent e)
{
if (e.getSource() == cbType) {
cbCaseSensitive.setEnabled(cbType.getSelectedIndex() == 0);
}
}
//--------------------- End Interface ItemListener ---------------------
//--------------------- Begin Interface DocumentListener ---------------------
@Override
public void insertUpdate(DocumentEvent e)
{
bOk.setEnabled(!tfSearch.getText().isEmpty());
}
@Override
public void removeUpdate(DocumentEvent e)
{
bOk.setEnabled(!tfSearch.getText().isEmpty());
}
@Override
public void changedUpdate(DocumentEvent e)
{
}
//--------------------- End Interface DocumentListener ---------------------
/** Displays a Find dialog and returns whether the Find action has been initiated. */
public boolean find()
{
tfSearch.requestFocusInWindow();
tfSearch.selectAll();
setVisible(true);
return retVal;
}
/** Returns whether to consider cases when searching text. */
public boolean isCaseSensitive()
{
return cbCaseSensitive.isSelected();
}
/** Returns the selected data type. */
public Type getDataType()
{
return (cbType.getSelectedIndex() == 0) ? Type.TEXT : Type.BYTES;
}
/**
* Returns the unprocessed text string. Can be used as is for text type.
* Use {@link #getBytes()} to return the parsed byte data.
*/
public String getText()
{
return text;
}
/**
* Returns the interpreted byte data from the search string if data type "Byte Values" is selected.
* Returns an empty byte array otherwise.
*/
public byte[] getBytes()
{
return bytes;
}
private void init(Window parent)
{
setDefaultCloseOperation(WindowConstants.HIDE_ON_CLOSE);
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
// Options
JPanel pOptions = new JPanel(new GridBagLayout());
cbCaseSensitive = new JCheckBox("Case sensitive", false);
cbCaseSensitive.setMnemonic('c');
gbc = ViewerUtil.setGBC(gbc, 0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START,
GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0);
pOptions.add(cbCaseSensitive, gbc);
// Buttons
JPanel pButtons = new JPanel(new GridBagLayout());
bOk = new JButton("OK");
bOk.addActionListener(this);
bOk.setEnabled(false);
bCancel = new JButton("Cancel");
bCancel.addActionListener(this);
bOk.setPreferredSize(bCancel.getPreferredSize());
gbc = ViewerUtil.setGBC(gbc, 0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START,
GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0);
pButtons.add(new JPanel(), gbc);
gbc = ViewerUtil.setGBC(gbc, 1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START,
GridBagConstraints.HORIZONTAL, new Insets(0, 4, 0, 0), 0, 0);
pButtons.add(bOk, gbc);
gbc = ViewerUtil.setGBC(gbc, 2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START,
GridBagConstraints.HORIZONTAL, new Insets(0, 4, 0, 0), 0, 0);
pButtons.add(bCancel, gbc);
// putting all together
JPanel pMain = new JPanel(new GridBagLayout());
JLabel lSearch = new JLabel("Search for:", SwingConstants.RIGHT);
JLabel lType = new JLabel("Datatype:", SwingConstants.RIGHT);
tfSearch = new JTextField();
tfSearch.getDocument().addDocumentListener(this);
cbType = new JComboBox<>(new String[]{"Text String", "Hex Values"});
cbType.addItemListener(this);
gbc = ViewerUtil.setGBC(gbc, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START,
GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0);
pMain.add(lSearch, gbc);
gbc = ViewerUtil.setGBC(gbc, 1, 0, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START,
GridBagConstraints.HORIZONTAL, new Insets(0, 8, 0, 0), 0, 0);
pMain.add(tfSearch, gbc);
gbc = ViewerUtil.setGBC(gbc, 0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START,
GridBagConstraints.HORIZONTAL, new Insets(4, 0, 0, 0), 0, 0);
pMain.add(lType, gbc);
gbc = ViewerUtil.setGBC(gbc, 1, 1, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START,
GridBagConstraints.HORIZONTAL, new Insets(4, 8, 0, 0), 0, 0);
pMain.add(cbType, gbc);
gbc = ViewerUtil.setGBC(gbc, 0, 2, 1, 1, 0.0, 1.0, GridBagConstraints.FIRST_LINE_START,
GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0);
pMain.add(new JPanel(), gbc);
gbc = ViewerUtil.setGBC(gbc, 1, 2, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START,
GridBagConstraints.HORIZONTAL, new Insets(4, 4, 0, 0), 0, 0);
pMain.add(pOptions, gbc);
gbc = ViewerUtil.setGBC(gbc, 0, 3, 3, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_END,
GridBagConstraints.HORIZONTAL, new Insets(8, 0, 0, 0), 0, 0);
pMain.add(pButtons, gbc);
gbc = ViewerUtil.setGBC(gbc, 0, 0, 1, 1, 1.0, 1.0, GridBagConstraints.FIRST_LINE_END,
GridBagConstraints.BOTH, new Insets(8, 8, 8, 8), 0, 0);
add(pMain, gbc);
// finalizing dialog content
pack();
Dimension d = getPreferredSize();
setMinimumSize(new Dimension(d.width, d.height));
setPreferredSize(new Dimension(d.width * 3 / 2, d.height));
pack();
setLocationRelativeTo(parent);
// setting up shortcuts
final InputMap inputMap = getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
final ActionMap actionMap = getRootPane().getActionMap();
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "Enter");
actionMap.put("Enter", new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e)
{
bOk.doClick();
}
});
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "Escape");
actionMap.put("Escape", new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e)
{
bCancel.doClick();
}
});
}
// Attempts to parse useful byte values from the specified text string
private byte[] parseBytes(String text)
{
List<Byte> list = new ArrayList<Byte>();
// parsing text string
StringBuilder sb = new StringBuilder();
for (int idx = 0; idx < text.length(); idx++) {
char ch = text.charAt(idx);
if (Character.digit(ch, 16) >= 0) {
sb.append(ch);
} else if (!Character.isWhitespace(ch)) {
idx = text.length() - 1; // skip to end
}
if (sb.length() == 2 || (idx+1 == text.length() && sb.length() > 0)) {
try {
int value = Integer.parseInt(sb.toString(), 16);
list.add(Byte.valueOf((byte)value));
sb.delete(0, sb.length());
} catch (NumberFormatException e) {
break;
}
}
}
// putting values into byte array
byte[] retVal = new byte[list.size()];
for (int i = 0; i < retVal.length; i++) {
retVal[i] = list.get(i).byteValue();
}
return retVal;
}
}