package ij.plugin;
import ij.*;
import ij.text.*;
import ij.plugin.frame.Editor;
import ij.process.ImageProcessor;
import ij.gui.GUI;
import ij.gui.HTMLDialog;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.io.File;
import javax.swing.*;
import javax.swing.table.*;
import javax.swing.event.*;
import javax.swing.event.DocumentEvent;
public class CommandFinder implements PlugIn, ActionListener, WindowListener, KeyListener, ItemListener, MouseListener {
private static final int TABLE_WIDTH = 640;
private static final int TABLE_ROWS = 18;
private int multiClickInterval;
private long lastClickTime;
private static JFrame frame;
private JTextField prompt;
private JScrollPane scrollPane;
private JButton runButton, sourceButton, closeButton, commandsButton, helpButton;
private JCheckBox closeCheckBox;
private JCheckBox lutCheckBox;
private Hashtable commandsHash;
private String[] commands;
private static boolean closeWhenRunning = Prefs.get("command-finder.close", false);
private static boolean applyLUT;
private JTable table;
private TableModel tableModel;
private int lastClickedRow;
public CommandFinder() {
Toolkit toolkit = Toolkit.getDefaultToolkit();
Integer interval = (Integer) toolkit.getDesktopProperty("awt.multiClickInterval");
if (interval == null)
multiClickInterval = 300;
else
multiClickInterval = interval.intValue();
}
class CommandAction {
CommandAction(String classCommand, MenuItem menuItem, String menuLocation) {
this.classCommand = classCommand;
this.menuItem = menuItem;
this.menuLocation = menuLocation;
}
String classCommand;
MenuItem menuItem;
String menuLocation;
public String toString() {
return "classCommand: " + classCommand + ", menuItem: " + menuItem + ", menuLocation: " + menuLocation;
}
}
protected String[] makeRow(String command, CommandAction ca) {
String[] result = new String[tableModel.getColumnCount()];
result[0] = command;
if (ca.menuLocation != null)
result[1] = ca.menuLocation;
if (ca.classCommand != null)
result[2] = ca.classCommand;
String jarFile = Menus.getJarFileForMenuEntry(command);
if (jarFile != null)
result[3] = jarFile;
return result;
}
protected void populateList(String matchingSubstring) {
String substring = matchingSubstring.toLowerCase();
ArrayList list = new ArrayList();
int count = 0;
for (int i = 0; i < commands.length; ++i) {
String commandName = commands[i];
String command = commandName.toLowerCase();
CommandAction ca = (CommandAction) commandsHash.get(commandName);
String menuPath = ca.menuLocation;
if (menuPath == null)
menuPath = "";
menuPath = menuPath.toLowerCase();
if (command.indexOf(substring) >= 0 || menuPath.indexOf(substring) >= 0) {
String[] row = makeRow(commandName, ca);
list.add(row);
}
}
tableModel.setData(list);
prompt.requestFocus();
}
public void actionPerformed(ActionEvent ae) {
Object source = ae.getSource();
if (source == runButton) {
int row = table.getSelectedRow();
if (row < 0) {
error("Please select a command to run");
return;
}
runCommand(tableModel.getCommand(row));
} else if (source == sourceButton) {
int row = table.getSelectedRow();
if (row < 0) {
error("Please select a command");
return;
}
showSource(tableModel.getCommand(row));
} else if (source == closeButton) {
closeWindow();
} else if (source == commandsButton) {
IJ.doCommand("Commands...");
} else if (source == helpButton) {
String text = "<html>Shortcuts:<br>" + " ↑ ↓  Select items<br>"
+ " ↵  Open item<br>" + " A-Z  Alphabetic scroll<br>"
+ " ⌫ Activate search field</html>";
new HTMLDialog("", text);
}
}
public void itemStateChanged(ItemEvent ie) {
populateList(prompt.getText());
applyLUT = lutCheckBox.isSelected();
if (applyLUT)
prompt.setText("Lookup Tables");
}
public void mouseClicked(MouseEvent e) {
long now = System.currentTimeMillis();
int row = table.getSelectedRow();
long thisClickInterval = now - lastClickTime;
if (thisClickInterval < multiClickInterval) {
if (row >= 0 && lastClickedRow >= 0 && row == lastClickedRow)
runCommand(tableModel.getCommand(row));
}
lastClickTime = now;
lastClickedRow = row;
if (lutCheckBox.isSelected())
previewLUT();
}
public void mousePressed(MouseEvent e) {
}
public void mouseReleased(MouseEvent e) {
}
public void mouseEntered(MouseEvent e) {
}
public void mouseExited(MouseEvent e) {
}
void showSource(String cmd) {
if (showMacro(cmd))
return;
Hashtable table = Menus.getCommands();
String className = (String) table.get(cmd);
if (IJ.debugMode)
IJ.log("showSource: " + cmd + " " + className);
if (className == null) {
error("No source associated with this command:\n " + cmd);
return;
}
int mstart = className.indexOf("ij.plugin.Macro_Runner(\"");
if (mstart >= 0) { int mend = className.indexOf("\")");
if (mend == -1)
return;
String macro = className.substring(mstart + 24, mend);
IJ.open(IJ.getDirectory("plugins") + macro);
return;
}
if (className.endsWith("\")")) {
int openParen = className.lastIndexOf("(\"");
if (openParen > 0)
className = className.substring(0, openParen);
}
if (className.startsWith("ij.")) {
className = className.replaceAll("\\.", "/");
IJ.runPlugIn("ij.plugin.BrowserLauncher", IJ.URL + "/source/" + className + ".java");
return;
}
className = IJ.getDirectory("plugins") + className.replaceAll("\\.", "/");
String path = className + ".java";
File f = new File(path);
if (f.exists()) {
IJ.open(path);
return;
}
error("Unable to display source for this plugin:\n " + className);
}
private boolean showMacro(String cmd) {
String name = null;
if (cmd.equals("Display LUTs"))
name = "ShowAllLuts.txt";
else if (cmd.equals("Search..."))
name = "Search.txt";
if (name == null)
return false;
String code = BatchProcessor.openMacroFromJar(name);
if (code != null) {
Editor ed = new Editor();
ed.setSize(700, 600);
ed.create(name, code);
return true;
}
return false;
}
private void error(String msg) {
IJ.error("Command Finder", msg);
}
protected void runCommand(String command) {
IJ.showStatus("Running command " + command);
IJ.doCommand(command);
closeWhenRunning = closeCheckBox.isSelected();
if (closeWhenRunning)
closeWindow();
}
public void keyPressed(KeyEvent ke) {
int key = ke.getKeyCode();
int flags = ke.getModifiers();
int items = tableModel.getRowCount();
Object source = ke.getSource();
boolean meta = ((flags & KeyEvent.META_MASK) != 0) || ((flags & KeyEvent.CTRL_MASK) != 0);
if (key == KeyEvent.VK_ESCAPE || (key == KeyEvent.VK_W && meta)) {
closeWindow();
} else if (source == prompt) {
if (key == KeyEvent.VK_ENTER) {
if (1 == items)
runCommand(tableModel.getCommand(0));
}
int index = -1;
if (key == KeyEvent.VK_UP) {
index = table.getSelectedRow() - 1;
if (index < 0)
index = items - 1;
} else if (key == KeyEvent.VK_DOWN) {
index = table.getSelectedRow() + 1;
if (index >= items)
index = Math.min(items - 1, 0);
}
if (index >= 0) {
table.requestFocus();
table.setRowSelectionInterval(index, index);
}
} else if (key == KeyEvent.VK_BACK_SPACE || key == KeyEvent.VK_DELETE) {
prompt.requestFocus();
} else if (source == table) {
if (key == KeyEvent.VK_ENTER) {
ke.consume();
int row = table.getSelectedRow();
if (row >= 0)
runCommand(tableModel.getCommand(row));
} else if (key == KeyEvent.VK_UP) {
if (table.getSelectedRow() == 0)
table.setRowSelectionInterval(tableModel.getRowCount() - 1, tableModel.getRowCount() - 1);
} else if (key == KeyEvent.VK_DOWN) {
if (table.getSelectedRow() == tableModel.getRowCount() - 1)
table.setRowSelectionInterval(0, 0);
}
}
}
public void keyReleased(KeyEvent ke) {
if (lutCheckBox.isSelected())
previewLUT();
}
public void previewLUT() {
int row = table.getSelectedRow();
if (row >= 0) {
String cmd = tableModel.getCommand(row);
String mPath = (String) tableModel.getValueAt(row, 1);
String cName = (String) tableModel.getValueAt(row, 2);
if ((mPath.indexOf("Lookup Table") > 0) && ((null == cName) || (cName.indexOf("LutLoader") > 0))) {
ImagePlus imp = WindowManager.getCurrentImage();
if (null == imp) {
imp = IJ.createImage("LUT Preview", "8-bit ramp", 256, 32, 1);
imp.show();
}
if (imp.getBitDepth() != 24) {
if (imp.isComposite())
((CompositeImage)imp).setChannelColorModel(LutLoader.getLut(cmd));
else {
ImageProcessor ip = imp.getProcessor();
ip.setColorModel(LutLoader.getLut(cmd));
IJ.showStatus(cmd);
}
imp.updateAndDraw();
}
}
}
}
public void keyTyped(KeyEvent ke) {
}
class PromptDocumentListener implements DocumentListener {
public void insertUpdate(DocumentEvent e) {
populateList(prompt.getText());
}
public void removeUpdate(DocumentEvent e) {
populateList(prompt.getText());
}
public void changedUpdate(DocumentEvent e) {
populateList(prompt.getText());
}
}
public void parseMenu(String path, Menu menu) {
int n = menu.getItemCount();
for (int i = 0; i < n; ++i) {
MenuItem m = menu.getItem(i);
String label = m.getActionCommand();
if (m instanceof Menu) {
Menu subMenu = (Menu) m;
parseMenu(path + ">" + label, subMenu);
} else {
String trimmedLabel = label.trim();
if (trimmedLabel.length() == 0 || trimmedLabel.equals("-"))
continue;
CommandAction ca = (CommandAction) commandsHash.get(label);
if (ca == null)
commandsHash.put(label, new CommandAction(null, m, path));
else {
ca.menuItem = m;
ca.menuLocation = path;
}
CommandAction caAfter = (CommandAction) commandsHash.get(label);
}
}
}
public void findAllMenuItems() {
MenuBar menuBar = Menus.getMenuBar();
int topLevelMenus = menuBar.getMenuCount();
for (int i = 0; i < topLevelMenus; ++i) {
Menu topLevelMenu = menuBar.getMenu(i);
parseMenu(topLevelMenu.getLabel(), topLevelMenu);
}
}
public void run(String initialSearch) {
if (frame != null) {
if (initialSearch != null && !initialSearch.isEmpty()) {
frame.dispose(); } else {
WindowManager.toFront(frame);
return;
}
}
commandsHash = new Hashtable();
Hashtable realCommandsHash = (Hashtable) (ij.Menus.getCommands().clone());
Set realCommandSet = realCommandsHash.keySet();
for (Iterator i = realCommandSet.iterator(); i.hasNext();) {
String command = (String) i.next();
String trimmedCommand = command.trim();
if (trimmedCommand.length() > 0 && !trimmedCommand.equals("-")) {
commandsHash.put(command, new CommandAction((String) realCommandsHash.get(command), null, null));
}
}
findAllMenuItems();
commands = (String[]) commandsHash.keySet().toArray(new String[0]);
Arrays.sort(commands);
ImageJ imageJ = IJ.getInstance();
frame = new JFrame("Command Finder") {
public void setVisible(boolean visible) {
if (visible)
WindowManager.addWindow(this);
super.setVisible(visible);
}
public void dispose() {
WindowManager.removeWindow(this);
Prefs.set("command-finder.close", closeWhenRunning);
frame = null;
super.dispose();
}
};
Container contentPane = frame.getContentPane();
contentPane.setLayout(new BorderLayout());
frame.addWindowListener(this);
if (imageJ != null && !IJ.isMacOSX()) {
Image img = imageJ.getIconImage();
if (img != null)
try {
frame.setIconImage(img);
} catch (Exception e) {
}
}
closeCheckBox = new JCheckBox("Close window after running command", closeWhenRunning);
GUI.scale(closeCheckBox);
closeCheckBox.addItemListener(this);
lutCheckBox = new JCheckBox("Apply LUTs", applyLUT);
GUI.scale(lutCheckBox);
lutCheckBox.addItemListener(this);
JPanel northPanel = new JPanel(new BorderLayout());
JLabel searchLabel = new JLabel(" Search:");
GUI.scale(searchLabel);
northPanel.add(searchLabel, BorderLayout.WEST);
prompt = new JTextField("", 20);
GUI.scale(prompt);
prompt.getDocument().addDocumentListener(new PromptDocumentListener());
prompt.addKeyListener(this);
northPanel.add(prompt);
contentPane.add(northPanel, BorderLayout.NORTH);
tableModel = new TableModel();
table = new JTable(tableModel);
table.setRowSelectionAllowed(true);
table.setColumnSelectionAllowed(false);
tableModel.setColumnWidths(table.getColumnModel());
GUI.scale(table);
Dimension dim = new Dimension(TABLE_WIDTH, table.getRowHeight() * TABLE_ROWS);
table.setPreferredScrollableViewportSize(dim);
table.addKeyListener(this);
table.addMouseListener(this);
table.addKeyListener(new KeyAdapter() {
public void keyTyped(final KeyEvent evt) {
if (evt.isControlDown() || evt.isMetaDown())
return;
final int nRows = tableModel.getRowCount();
final char ch = Character.toLowerCase(evt.getKeyChar());
if (!Character.isLetterOrDigit(ch)) {
return; }
final int sRow = table.getSelectedRow();
for (int row = (sRow + 1) % nRows; row != sRow; row = (row + 1) % nRows) {
final String rowData = tableModel.getValueAt(row, 0).toString();
final char rowCh = Character.toLowerCase(rowData.charAt(0));
if (ch == rowCh) {
table.setRowSelectionInterval(row, row);
table.scrollRectToVisible(table.getCellRect(row, 0, true));
break;
}
}
}
});
scrollPane = new JScrollPane(table);
if (initialSearch == null)
initialSearch = "";
prompt.setText(initialSearch);
populateList(initialSearch);
contentPane.add(scrollPane, BorderLayout.CENTER);
runButton = new JButton("Run");
GUI.scale(runButton);
sourceButton = new JButton("Source");
GUI.scale(sourceButton);
closeButton = new JButton("Close");
GUI.scale(closeButton);
commandsButton = new JButton("Commands");
GUI.scale(commandsButton);
helpButton = new JButton("Help");
GUI.scale(helpButton);
runButton.addActionListener(this);
sourceButton.addActionListener(this);
closeButton.addActionListener(this);
commandsButton.addActionListener(this);
helpButton.addActionListener(this);
runButton.addKeyListener(this);
sourceButton.addKeyListener(this);
closeButton.addKeyListener(this);
commandsButton.addKeyListener(this);
helpButton.addKeyListener(this);
JPanel southPanel = new JPanel();
southPanel.setLayout(new BorderLayout());
JPanel optionsPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 0, 0));
optionsPanel.add(closeCheckBox);
optionsPanel.add(lutCheckBox);
JPanel buttonsPanel = new JPanel();
buttonsPanel.add(runButton);
buttonsPanel.add(sourceButton);
buttonsPanel.add(closeButton);
buttonsPanel.add(commandsButton);
buttonsPanel.add(helpButton);
southPanel.add(optionsPanel, BorderLayout.CENTER);
southPanel.add(buttonsPanel, BorderLayout.SOUTH);
contentPane.add(southPanel, BorderLayout.SOUTH);
Rectangle screen = GUI.getMaxWindowBounds(IJ.getInstance());
frame.pack();
int dialogWidth = frame.getWidth();
int dialogHeight = frame.getHeight();
Point pos = imageJ.getLocationOnScreen();
Dimension size = imageJ.getSize();
int initialX = pos.x + 10;
int initialY = pos.y + 10 + size.height;
initialX = Math.max(screen.x, Math.min(initialX, screen.x + screen.width - dialogWidth));
initialY = Math.max(screen.y, Math.min(initialY, screen.y + screen.height - dialogHeight));
frame.setLocation(initialX, initialY);
frame.setVisible(true);
frame.toFront();
}
public void windowClosing(WindowEvent e) {
closeWindow();
}
private void closeWindow() {
if (frame != null)
frame.dispose();
}
public void windowActivated(WindowEvent e) {
if (IJ.isMacOSX() && frame != null)
frame.setMenuBar(Menus.getMenuBar());
}
public void windowDeactivated(WindowEvent e) {
}
public void windowClosed(WindowEvent e) {
}
public void windowOpened(WindowEvent e) {
}
public void windowIconified(WindowEvent e) {
}
public void windowDeiconified(WindowEvent e) {
}
private class TableModel extends AbstractTableModel {
protected ArrayList list;
public final static int COLUMNS = 4;
public TableModel() {
list = new ArrayList();
}
public void setData(ArrayList list) {
this.list = list;
fireTableDataChanged();
}
public int getColumnCount() {
return COLUMNS;
}
public String getColumnName(int column) {
switch (column) {
case 0:
return "Command";
case 1:
return "Menu Path";
case 2:
return "Class";
case 3:
return "File";
}
return null;
}
public int getRowCount() {
return list.size();
}
public Object getValueAt(int row, int column) {
if (row >= list.size() || column >= COLUMNS)
return null;
String[] strings = (String[]) list.get(row);
return strings[column];
}
public String getCommand(int row) {
if (row < 0 || row >= list.size())
return "";
else
return (String) getValueAt(row, 0);
}
public void setColumnWidths(TableColumnModel columnModel) {
int[] widths = { 170, 150, 170, 30 };
for (int i = 0; i < widths.length; i++)
columnModel.getColumn(i).setPreferredWidth(widths[i]);
}
}
}