diff --git a/ij/IJ.java b/ij/IJ.java index 36443687..e87537f0 100644 --- a/ij/IJ.java +++ b/ij/IJ.java @@ -123,7 +123,7 @@ else if (version.startsWith("1.7")) df[9] = new DecimalFormat("0.000000000", dfs); df[0].setRoundingMode(RoundingMode.HALF_UP); } - + static void init(ImageJ imagej, Applet theApplet) { ij = imagej; applet = theApplet; @@ -441,7 +441,11 @@ public static boolean isMacro() { return macroRunning || Interpreter.getInstance()!=null; } - /**Returns the Applet that created this ImageJ or null if running as an application.*/ + /** + * Returns the Applet that created this ImageJ or null if running as an application. + * @deprecated Always returns null when running on Java 26+ due to removal of Applets. + */ + @Deprecated public static java.applet.Applet getApplet() { return applet; } diff --git a/ij/ImageJ.java b/ij/ImageJ.java index 5b2e02cb..0f6e0489 100644 --- a/ij/ImageJ.java +++ b/ij/ImageJ.java @@ -136,14 +136,19 @@ public ImageJ(int mode) { this(null, mode); } - /** Creates a new ImageJ frame that runs as an applet. */ + /** Creates a new ImageJ frame that runs as an applet. + @deprecated Applets were removed in Java 26*/ + @Deprecated public ImageJ(java.applet.Applet applet) { this(applet, STANDALONE); } /** If 'applet' is not null, creates a new ImageJ frame that runs as an applet. If 'mode' is ImageJ.EMBEDDED and 'applet is null, creates an embedded - (non-standalone) version of ImageJ. */ + (non-standalone) version of ImageJ. + @deprecated Applets were removed in Java 26. + */ + @Deprecated public ImageJ(java.applet.Applet applet, int mode) { super("ImageJ"); if ((mode&DEBUG)!=0) diff --git a/ij/Prefs.java b/ij/Prefs.java index 825f118e..a2259968 100644 --- a/ij/Prefs.java +++ b/ij/Prefs.java @@ -295,11 +295,24 @@ public static boolean get(String key, boolean defaultValue) { else return value.equals("true"); } + + /** + * Finds and loads the configuration file ("IJ_Props.txt") + * and the preferences file ("IJ_Prefs.txt"). + * + * @param ij + * @return an error message if "IJ_Props.txt" not found. + */ + public static String load(Object ij) { + return load(ij, null); + } /** Finds and loads the configuration file ("IJ_Props.txt") * and the preferences file ("IJ_Prefs.txt"). * @return an error message if "IJ_Props.txt" not found. + * @deprecated Prefer {@link #load(Object)} as Applets were removed in Java 26. */ + @Deprecated public static String load(Object ij, Applet applet) { if (ImageJDir==null) ImageJDir = System.getProperty("user.dir"); @@ -345,6 +358,7 @@ static void dumpPrefs() { } */ + @Deprecated static String loadAppletProps(InputStream f, Applet applet) { if (f==null) return PROPS_NAME+" not found in ij.jar"; diff --git a/mrj/26/ij/IJ.java b/mrj/26/ij/IJ.java new file mode 100644 index 00000000..cc500e9b --- /dev/null +++ b/mrj/26/ij/IJ.java @@ -0,0 +1,2579 @@ +package ij; + +import ij.gui.*; +import ij.process.*; +import ij.stub.Applet; +import ij.text.*; +import ij.io.*; +import ij.plugin.*; +import ij.plugin.filter.*; +import ij.util.Tools; +import ij.plugin.frame.Recorder; +import ij.plugin.frame.ThresholdAdjuster; +import ij.macro.Interpreter; +import ij.macro.MacroRunner; +import ij.measure.Calibration; +import ij.measure.ResultsTable; +import ij.measure.Measurements; +import java.awt.event.*; +import java.text.*; +import java.util.*; +import java.awt.*; +import java.io.*; +import java.net.*; +import java.nio.ByteBuffer; +import java.math.RoundingMode; + + +/** This class consists of static utility methods. */ +public class IJ { + + /** SansSerif, plain, 10-point font */ + public static Font font10 = new Font("SansSerif", Font.PLAIN, 10); + /** SansSerif, plain, 12-point font */ + public static Font font12 = ImageJ.SansSerif12; + /** SansSerif, plain, 14-point font */ + public static Font font14 = ImageJ.SansSerif14; + + /** Image display modes */ + public static final int COMPOSITE=1, COLOR=2, GRAYSCALE=3; + + /** @deprecated */ + public static final String URL = "http://imagej.net/ij"; + + /** ImageJ website */ + public static final String URL2 = "http://imagej.net/ij"; + + public static final int ALL_KEYS = -1; + + /** Use setDebugMode(boolean) to enable/disable debug mode. */ + public static boolean debugMode; + + public static boolean hideProcessStackDialog; + + public static final char micronSymbol = '\u00B5'; + public static final char angstromSymbol = '\u00C5'; + public static final char degreeSymbol = '\u00B0'; + + private static ImageJ ij; + private static Applet applet; + private static ProgressBar progressBar; + private static TextPanel textPanel; + private static String osname, osarch; + private static boolean isMac, isWin, isLinux, is64Bit; + private static int javaVersion; + private static boolean controlDown, altDown, spaceDown, shiftDown; + private static boolean macroRunning; + private static Thread previousThread; + private static TextPanel logPanel; + private static ClassLoader classLoader; + private static boolean memMessageDisplayed; + private static long maxMemory; + private static boolean escapePressed; + private static boolean redirectErrorMessages; + private static boolean suppressPluginNotFoundError; + private static Hashtable commandTable; + private static Vector eventListeners = new Vector(); + private static String lastErrorMessage; + private static Properties properties; private static DecimalFormat[] df; + private static DecimalFormat[] sf; + private static DecimalFormatSymbols dfs; + private static boolean trustManagerCreated; + private static String smoothMacro; + private static Interpreter macroInterpreter; + private static boolean protectStatusBar; + private static Thread statusBarThread; + + static { + osname = System.getProperty("os.name"); + isWin = osname.startsWith("Windows"); + isMac = !isWin && osname.startsWith("Mac"); + isLinux = osname.startsWith("Linux"); + String version = System.getProperty("java.version"); + if (version==null || version.length()<2) + version = "1.8"; + if (version.startsWith("1.8")) + javaVersion = 8; + else if (version.charAt(0)=='1' && Character.isDigit(version.charAt(1))) + javaVersion = 10 + (version.charAt(1) - '0'); + else if (version.charAt(0)=='2' && Character.isDigit(version.charAt(1))) + javaVersion = 20 + (version.charAt(1) - '0'); + else if (version.startsWith("1.6")) + javaVersion = 6; + else if (version.startsWith("1.9")||version.startsWith("9")) + javaVersion = 9; + else if (version.startsWith("1.7")) + javaVersion = 7; + else + javaVersion = 8; + dfs = new DecimalFormatSymbols(Locale.US); + df = new DecimalFormat[10]; + df[0] = new DecimalFormat("0", dfs); + df[1] = new DecimalFormat("0.0", dfs); + df[2] = new DecimalFormat("0.00", dfs); + df[3] = new DecimalFormat("0.000", dfs); + df[4] = new DecimalFormat("0.0000", dfs); + df[5] = new DecimalFormat("0.00000", dfs); + df[6] = new DecimalFormat("0.000000", dfs); + df[7] = new DecimalFormat("0.0000000", dfs); + df[8] = new DecimalFormat("0.00000000", dfs); + df[9] = new DecimalFormat("0.000000000", dfs); + df[0].setRoundingMode(RoundingMode.HALF_UP); + } + + static void init(ImageJ imagej, Applet theApplet) { + ij = imagej; + applet = theApplet; + progressBar = ij.getProgressBar(); + } + + static void cleanup() { + ij=null; applet=null; progressBar=null; textPanel=null; + } + + /**Returns a reference to the "ImageJ" frame.*/ + public static ImageJ getInstance() { + return ij; + } + + /**Enable/disable debug mode.*/ + public static void setDebugMode(boolean b) { + debugMode = b; + LogStream.redirectSystem(debugMode); + } + + /** Runs the macro contained in the string macro + on the current thread. Returns any string value returned by + the macro, null if the macro does not return a value, or + "[aborted]" if the macro was aborted due to an error. The + equivalent macro function is eval(). */ + public static String runMacro(String macro) { + return runMacro(macro, ""); + } + + /** Runs the macro contained in the string macro + on the current thread. The optional string argument can be + retrieved in the called macro using the getArgument() macro + function. Returns any string value returned by the macro, null + if the macro does not return a value, or "[aborted]" if the + macro was aborted due to an error. */ + public static String runMacro(String macro, String arg) { + Macro_Runner mr = new Macro_Runner(); + return mr.runMacro(macro, arg); + } + + /** Runs the specified macro or script file in the current thread. + The file is assumed to be in the macros folder + unless name is a full path. + The optional string argument (arg) can be retrieved in the called + macro or script using the getArgument() function. + Returns any string value returned by the macro, or null. Scripts always return null. + The equivalent macro function is runMacro(). */ + public static String runMacroFile(String name, String arg) { + Macro_Runner mr = new Macro_Runner(); + return mr.runMacroFile(name, arg); + } + + /** Runs the specified macro file. */ + public static String runMacroFile(String name) { + return runMacroFile(name, null); + } + + /** Runs the specified plugin using the specified image. */ + public static Object runPlugIn(ImagePlus imp, String className, String arg) { + if (imp!=null) { + ImagePlus temp = WindowManager.getTempCurrentImage(); + WindowManager.setTempCurrentImage(imp); + Object o = runPlugIn("", className, arg); + WindowManager.setTempCurrentImage(temp); + return o; + } else + return runPlugIn(className, arg); + } + + /** Runs the specified plugin and returns a reference to it. */ + public static Object runPlugIn(String className, String arg) { + return runPlugIn("", className, arg); + } + + /** Runs the specified plugin and returns a reference to it. */ + public static Object runPlugIn(String commandName, String className, String arg) { + if (arg==null) arg = ""; + if (IJ.debugMode) + IJ.log("runPlugIn: "+className+argument(arg)); + // Load using custom classloader if this is a user + // plugin and we are not running as an applet + if (!className.startsWith("ij.") && applet==null) + return runUserPlugIn(commandName, className, arg, false); + Object thePlugIn=null; + try { + Class c = Class.forName(className); + thePlugIn = c.newInstance(); + if (thePlugIn instanceof PlugIn) + ((PlugIn)thePlugIn).run(arg); + else + new PlugInFilterRunner(thePlugIn, commandName, arg); + } catch (ClassNotFoundException e) { + if (!(className!=null && className.startsWith("ij.plugin.MacAdapter"))) { + log("Plugin or class not found: \"" + className + "\"\n(" + e+")"); + String path = Prefs.getCustomPropsPath(); + if (path!=null); + log("Error may be due to custom properties at " + path); + } + } + catch (InstantiationException e) {log("Unable to load plugin (ins)");} + catch (IllegalAccessException e) {log("Unable to load plugin, possibly \nbecause it is not public.");} + redirectErrorMessages = false; + return thePlugIn; + } + + static Object runUserPlugIn(String commandName, String className, String arg, boolean createNewLoader) { + if (IJ.debugMode) + IJ.log("runUserPlugIn: "+className+", arg="+argument(arg)); + if (applet!=null) return null; + if (createNewLoader) + classLoader = null; + ClassLoader loader = getClassLoader(); + Object thePlugIn = null; + try { + thePlugIn = (loader.loadClass(className)).newInstance(); + if (thePlugIn instanceof PlugIn) + ((PlugIn)thePlugIn).run(arg); + else if (thePlugIn instanceof PlugInFilter) + new PlugInFilterRunner(thePlugIn, commandName, arg); + } + catch (ClassNotFoundException e) { + if (className.startsWith("macro:")) + runMacro(className.substring(6)); + else if (className.contains("_") && !suppressPluginNotFoundError) + error("Plugin or class not found: \"" + className + "\"\n(" + e+")"); + } + catch (NoClassDefFoundError e) { + int dotIndex = className.indexOf('.'); + if (dotIndex>=0 && className.contains("_")) { + // rerun plugin after removing folder name + if (debugMode) IJ.log("runUserPlugIn: rerunning "+className); + return runUserPlugIn(commandName, className.substring(dotIndex+1), arg, createNewLoader); + } + if (className.contains("_") && !suppressPluginNotFoundError) + error("Run User Plugin", "Class not found while attempting to run \"" + className + "\"\n \n " + e); + } + catch (InstantiationException e) {error("Unable to load plugin (ins)");} + catch (IllegalAccessException e) {error("Unable to load plugin, possibly \nbecause it is not public.");} + if (thePlugIn!=null && !"HandleExtraFileTypes".equals(className)) + redirectErrorMessages = false; + suppressPluginNotFoundError = false; + return thePlugIn; + } + + private static String argument(String arg) { + return arg!=null && !arg.equals("") && !arg.contains("\n")?"(\""+arg+"\")":""; + } + + static void wrongType(int capabilities, String cmd) { + String s = "\""+cmd+"\" requires an image of type:\n \n"; + if ((capabilities&PlugInFilter.DOES_8G)!=0) s += " 8-bit grayscale\n"; + if ((capabilities&PlugInFilter.DOES_8C)!=0) s += " 8-bit color\n"; + if ((capabilities&PlugInFilter.DOES_16)!=0) s += " 16-bit grayscale\n"; + if ((capabilities&PlugInFilter.DOES_32)!=0) s += " 32-bit (float) grayscale\n"; + if ((capabilities&PlugInFilter.DOES_RGB)!=0) s += " RGB color\n"; + error(s); + } + + /** Runs a menu command on a separete thread and returns immediately. */ + public static void doCommand(String command) { + new Executer(command, null); + } + + /** Runs a menu command on a separete thread, using the specified image. */ + public static void doCommand(ImagePlus imp, String command) { + new Executer(command, imp); + } + + /** Runs an ImageJ command. Does not return until + the command has finished executing. To avoid "image locked", + errors, plugins that call this method should implement + the PlugIn interface instead of PlugInFilter. */ + public static void run(String command) { + run(command, null); + } + + /** Runs an ImageJ command, with options that are passed to the + GenericDialog and OpenDialog classes. Does not return until + the command has finished executing. To generate run() calls, + start the recorder (Plugins/Macro/Record) and run commands + from the ImageJ menu bar. + */ + public static void run(String command, String options) { + //IJ.log("run1: "+command+" "+Thread.currentThread().hashCode()+" "+options); + if (ij==null && Menus.getCommands()==null) + init(); + Macro.abort = false; + Macro.setOptions(options); + Thread thread = Thread.currentThread(); + if (previousThread==null || thread!=previousThread) { + String name = thread.getName(); + if (!name.startsWith("Run$_")) + thread.setName("Run$_"+name); + } + command = convert(command); + previousThread = thread; + macroRunning = true; + Executer e = new Executer(command); + e.run(); + macroRunning = false; + Macro.setOptions(null); + testAbort(); + macroInterpreter = null; + //IJ.log("run2: "+command+" "+Thread.currentThread().hashCode()); + } + + /** The macro interpreter uses this method to run commands. */ + public static void run(Interpreter interpreter, String command, String options) { + macroInterpreter = interpreter; + run(command, options); + macroInterpreter = null; + } + + /** Converts commands that have been renamed so + macros using the old names continue to work. */ + private static String convert(String command) { + if (commandTable==null) { + commandTable = new Hashtable(30); + commandTable.put("New...", "Image..."); + commandTable.put("Threshold", "Make Binary"); + commandTable.put("Display...", "Appearance..."); + commandTable.put("Start Animation", "Start Animation [\\]"); + commandTable.put("Convert Images to Stack", "Images to Stack"); + commandTable.put("Convert Stack to Images", "Stack to Images"); + commandTable.put("Convert Stack to RGB", "Stack to RGB"); + commandTable.put("Convert to Composite", "Make Composite"); + commandTable.put("RGB Split", "Split Channels"); + commandTable.put("RGB Merge...", "Merge Channels..."); + commandTable.put("Channels...", "Channels Tool..."); + commandTable.put("New... ", "Table..."); + commandTable.put("Arbitrarily...", "Rotate... "); + commandTable.put("Measurements...", "Results... "); + commandTable.put("List Commands...", "Find Commands..."); + commandTable.put("Capture Screen ", "Capture Screen"); + commandTable.put("Add to Manager ", "Add to Manager"); + commandTable.put("In", "In [+]"); + commandTable.put("Out", "Out [-]"); + commandTable.put("Enhance Contrast", "Enhance Contrast..."); + commandTable.put("XY Coodinates... ", "XY Coordinates... "); + commandTable.put("Statistics...", "Statistics"); + commandTable.put("Channels Tool... ", "Channels Tool..."); + commandTable.put("Profile Plot Options...", "Plots..."); + commandTable.put("AuPbSn 40 (56K)", "AuPbSn 40"); + commandTable.put("Bat Cochlea Volume (19K)", "Bat Cochlea Volume"); + commandTable.put("Bat Cochlea Renderings (449K)", "Bat Cochlea Renderings"); + commandTable.put("Blobs (25K)", "Blobs"); + commandTable.put("Boats (356K)", "Boats"); + commandTable.put("Cardio (768K, RGB DICOM)", "Cardio (RGB DICOM)"); + commandTable.put("Cell Colony (31K)", "Cell Colony"); + commandTable.put("Clown (14K)", "Clown"); + commandTable.put("Confocal Series (2.2MB)", "Confocal Series"); + commandTable.put("CT (420K, 16-bit DICOM)", "CT (16-bit DICOM)"); + commandTable.put("Dot Blot (7K)", "Dot Blot"); + commandTable.put("Embryos (42K)", "Embryos"); + commandTable.put("Fluorescent Cells (400K)", "Fluorescent Cells"); + commandTable.put("Fly Brain (1MB)", "Fly Brain"); + commandTable.put("Gel (105K)", "Gel"); + commandTable.put("HeLa Cells (1.3M, 48-bit RGB)", "HeLa Cells (48-bit RGB)"); + commandTable.put("Leaf (36K)", "Leaf"); + commandTable.put("Line Graph (21K)", "Line Graph"); + commandTable.put("Mitosis (26MB, 5D stack)", "Mitosis (5D stack)"); + commandTable.put("MRI Stack (528K)", "MRI Stack"); + commandTable.put("M51 Galaxy (177K, 16-bits)", "M51 Galaxy (16-bits)"); + commandTable.put("Neuron (1.6M, 5 channels)", "Neuron (5 channels)"); + commandTable.put("Nile Bend (1.9M)", "Nile Bend"); + commandTable.put("Organ of Corti (2.8M, 4D stack)", "Organ of Corti (4D stack)"); + commandTable.put("Particles (75K)", "Particles"); + commandTable.put("T1 Head (2.4M, 16-bits)", "T1 Head (16-bits)"); + commandTable.put("T1 Head Renderings (736K)", "T1 Head Renderings"); + commandTable.put("TEM Filter (112K)", "TEM Filter"); + commandTable.put("Tree Rings (48K)", "Tree Rings"); + } + String command2 = (String)commandTable.get(command); + if (command2!=null) + return command2; + else + return command; + } + + /** Runs an ImageJ command using the specified image and options. + To generate run() calls, start the recorder (Plugins/Macro/Record) + and run commands from the ImageJ menu bar.*/ + public static void run(ImagePlus imp, String command, String options) { + if (ij==null && Menus.getCommands()==null) + init(); + if (imp!=null) { + ImagePlus temp = WindowManager.getTempCurrentImage(); + WindowManager.setTempCurrentImage(imp); + run(command, options); + WindowManager.setTempCurrentImage(temp); + } else + run(command, options); + } + + static void init() { + Menus m = new Menus(null, null); + Prefs.load(m, null); + m.addMenuBar(); + } + + private static void testAbort() { + if (Macro.abort) + abort(); + } + + /** Returns true if the run(), open() or newImage() method is executing. */ + public static boolean macroRunning() { + return macroRunning; + } + + /** Returns true if a macro is running, or if the run(), open() + or newImage() method is executing. */ + public static boolean isMacro() { + return macroRunning || Interpreter.getInstance()!=null; + } + + /** + * Returns the Applet that created this ImageJ or null if running as an application. + * @deprecated Always returns null since removal of Applets in Java 26. + */ + @Deprecated(since = "IJ XX; Java 26") + public static Applet getApplet() { + return null; + } + + /**Displays a message in the ImageJ status bar. If 's' starts + with '!', subsequent showStatus() calls in the current + thread (without "!" in the message) are suppressed. */ + public static void showStatus(String s) { + if ((Interpreter.getInstance()==null&&statusBarThread==null) + || (statusBarThread!=null&&Thread.currentThread()!=statusBarThread)) + protectStatusBar(false); + boolean doProtect = s.startsWith("!"); // suppress subsequent showStatus() calls + if (doProtect) { + protectStatusBar(true); + statusBarThread = Thread.currentThread(); + s = s.substring(1); + } + if (doProtect || !protectStatusBar) { + if (ij!=null) + ij.showStatus(s); + ImagePlus imp = WindowManager.getCurrentImage(); + ImageCanvas ic = imp!=null?imp.getCanvas():null; + if (ic!=null) + ic.setShowCursorStatus(s.length()==0?true:false); + } + } + + /**Displays a message in the status bar and flashes + * either the status bar or the active image.
+ * See: http://wsr.imagej.net/macros/FlashingStatusMessages.txt + */ + public static void showStatus(String message, String options) { + showStatus(message); + if (options==null) + return; + options = options.replace("flash", ""); + options = options.replace("ms", ""); + Color optionalColor = null; + int index1 = options.indexOf("#"); + if (index1>=0) { // hex color? + int index2 = options.indexOf(" ", index1); + if (index2==-1) index2 = options.length(); + String hexColor = options.substring(index1, index2); + optionalColor = Colors.decode(hexColor, null); + options = options.replace(hexColor, ""); + } + if (optionalColor==null) { // "red", "green", etc. + for (String c : Colors.colors) { + if (options.contains(c)) { + optionalColor = Colors.getColor(c, ImageJ.backgroundColor); + options = options.replace(c, ""); + break; + } + } + } + boolean flashImage = options.contains("image"); + Color defaultColor = new Color(255,255,245); + int defaultDelay = 500; + ImagePlus imp = WindowManager.getCurrentImage(); + if (flashImage) { + options = options.replace("image", ""); + if (imp!=null && imp.getWindow()!=null) { + defaultColor = Color.black; + defaultDelay = 100; + } + else + flashImage = false; + } + Color color = optionalColor!=null?optionalColor:defaultColor; + int delay = (int)Tools.parseDouble(options, defaultDelay); + if (delay>8000) + delay = 8000; + String colorString = null; + ImageJ ij = IJ.getInstance(); + if (flashImage) { + Color previousColor = imp.getWindow().getBackground(); + imp.getWindow().setBackground(color); + if (delay>0) { + wait(delay); + imp.getWindow().setBackground(previousColor); + } + } else if (ij!=null) { + ij.getStatusBar().setBackground(color); + wait(delay); + ij.getStatusBar().setBackground(ij.backgroundColor); + } + } + + /** + * @deprecated + * replaced by IJ.log(), ResultsTable.setResult() and TextWindow.append(). + * There are examples at + * http://imagej.net/ij/plugins/sine-cosine.html + */ + public static void write(String s) { + if (textPanel==null && ij!=null) + showResults(); + if (textPanel!=null) + textPanel.append(s); + else + System.out.println(s); + } + + private static void showResults() { + TextWindow resultsWindow = new TextWindow("Results", "", 400, 250); + textPanel = resultsWindow.getTextPanel(); + textPanel.setResultsTable(Analyzer.getResultsTable()); + } + + public static synchronized void log(String s) { + if (s==null) return; + if (logPanel==null && ij!=null) { + TextWindow logWindow = new TextWindow("Log", "", 400, 250); + logPanel = logWindow.getTextPanel(); + logPanel.setFont(new Font("SansSerif", Font.PLAIN, 16)); + } + if (logPanel!=null) { + if (s.startsWith("\\")) + handleLogCommand(s); + else { + if (s.endsWith("\n")) { + if (s.equals("\n\n")) + s= "\n \n "; + else if (s.endsWith("\n\n")) + s = s.substring(0, s.length()-2)+"\n \n "; + else + s = s+" "; + } + logPanel.append(s); + } + } else { + LogStream.redirectSystem(false); + System.out.println(s); + } + } + + static void handleLogCommand(String s) { + if (s.equals("\\Closed")) + logPanel = null; + else if (s.startsWith("\\Update:")) { + int n = logPanel.getLineCount(); + String s2 = s.substring(8, s.length()); + if (n==0) + logPanel.append(s2); + else + logPanel.setLine(n-1, s2); + } else if (s.startsWith("\\Update")) { + int cindex = s.indexOf(":"); + if (cindex==-1) + {logPanel.append(s); return;} + String nstr = s.substring(7, cindex); + int line = (int)Tools.parseDouble(nstr, -1); + if (line<0 || line>25) + {logPanel.append(s); return;} + int count = logPanel.getLineCount(); + while (line>=count) { + log(""); + count++; + } + String s2 = s.substring(cindex+1, s.length()); + logPanel.setLine(line, s2); + } else if (s.equals("\\Clear")) { + logPanel.clear(); + } else if (s.startsWith("\\Heading:")) { + logPanel.updateColumnHeadings(s.substring(10)); + } else if (s.equals("\\Close")) { + Frame f = WindowManager.getFrame("Log"); + if (f!=null && (f instanceof TextWindow)) + ((TextWindow)f).close(); + } else + logPanel.append(s); + } + + /** Returns the contents of the Log window or null if the Log window is not open. */ + public static synchronized String getLog() { + if (logPanel==null || ij==null) + return null; + else + return logPanel.getText(); + } + +/** Clears the "Results" window and sets the column headings to + those in the tab-delimited 'headings' String. Writes to + System.out.println if the "ImageJ" frame is not present.*/ + public static void setColumnHeadings(String headings) { + if (textPanel==null && ij!=null) + showResults(); + if (textPanel!=null) + textPanel.setColumnHeadings(headings); + else + System.out.println(headings); + } + + /** Returns true if the "Results" window is open. */ + public static boolean isResultsWindow() { + return textPanel!=null; + } + + /** Renames a results window. */ + public static void renameResults(String title) { + Frame frame = WindowManager.getFrontWindow(); + if (frame!=null && (frame instanceof TextWindow)) { + TextWindow tw = (TextWindow)frame; + if (tw.getResultsTable()==null) { + IJ.error("Rename", "\""+tw.getTitle()+"\" is not a results table"); + return; + } + tw.rename(title); + } else if (isResultsWindow()) { + TextPanel tp = getTextPanel(); + TextWindow tw = (TextWindow)tp.getParent(); + tw.rename(title); + } + } + + /** Changes the name of a table window from 'oldTitle' to 'newTitle'. */ + public static void renameResults(String oldTitle, String newTitle) { + Frame frame = WindowManager.getFrame(oldTitle); + if (frame==null) { + error("Rename", "\""+oldTitle+"\" not found"); + return; + } else if (frame instanceof TextWindow) { + TextWindow tw = (TextWindow)frame; + if (tw.getResultsTable()==null) { + error("Rename", "\""+oldTitle+"\" is not a table"); + return; + } + tw.rename(newTitle); + } else + error("Rename", "\""+oldTitle+"\" is not a table"); + } + + /** Deletes 'row1' through 'row2' of the "Results" window, where + 'row1' and 'row2' must be in the range 0-Analyzer.getCounter()-1. */ + public static void deleteRows(int row1, int row2) { + ResultsTable rt = Analyzer.getResultsTable(); + int tableSize = rt.size(); + rt.deleteRows(row1, row2); + ImagePlus imp = WindowManager.getCurrentImage(); + if (imp!=null) + Overlay.updateTableOverlay(imp, row1, row2, tableSize); + rt.show("Results"); + } + + /** Returns a measurement result, where 'measurement' is "Area", + * "Mean", "StdDev", "Mode", "Min", "Max", "X", "Y", "XM", "YM", + * "Perim.", "BX", "BY", "Width", "Height", "Major", "Minor", "Angle", + * "Circ.", "Feret", "IntDen", "Median", "Skew", "Kurt", "%Area", + * "RawIntDen", "Ch", "Slice", "Frame", "FeretX", "FeretY", + * "FeretAngle", "MinFeret", "AR", "Round", "Solidity", "MinThr" + * or "MaxThr". Add " raw" to the argument to disable calibration, + * for example IJ.getValue("Mean raw"). Add " limit" to enable + * the "limit to threshold" option. + */ + public static double getValue(ImagePlus imp, String measurement) { + String options = ""; + int index = measurement.indexOf(" "); + if (index>0) { + if (index>>>>>>>>>>>>>>>>>>>>>>>>>>"); + log(""); + if (!memMessageDisplayed) { + log(""); + log(""); + log("Options>Memory & Threads command.>"); + log(">>>>>>>>>>>>>>>>>>>>>>>>>>>"); + memMessageDisplayed = true; + } + Macro.abort(); + } + + /** Updates the progress bar, where 0<=progress<=1.0. The progress bar is + not shown in BatchMode and erased if progress>=1.0. The progress bar is + updated only if more than 90 ms have passes since the last call. Does nothing + if the ImageJ window is not present. */ + public static void showProgress(double progress) { + if (progressBar!=null) progressBar.show(progress, false); + } + + /** Updates the progress bar, where the length of the bar is set to + * (currentValue+1)/finalValue of the maximum bar length. + * The bar is erased if currentValue>=finalValue. + * The bar is updated only if more than 90 ms have passed since the + * last call. Displays subordinate progress bars as dots if + * 'currentIndex' is negative (example: Plugins/Utilities/Benchmark). + */ + public static void showProgress(int currentIndex, int finalIndex) { + if (progressBar!=null) { + progressBar.show(currentIndex, finalIndex); + if (currentIndex==finalIndex) + progressBar.setBatchMode(false); + } + } + + /** Displays a message in a dialog box titled "Message". + Writes the Java console if ImageJ is not present. */ + public static void showMessage(String msg) { + showMessage("Message", msg); + } + + /** Displays a message in a dialog box with the specified title. + Displays HTML formatted text if 'msg' starts with "". + There are examples at + "http://imagej.net/ij/macros/HtmlDialogDemo.txt". + Writes to the Java console if ImageJ is not present. */ + public static void showMessage(String title, String msg) { + if (ij!=null) { + if (msg!=null && (msg.startsWith("")||msg.startsWith(""))) { + HTMLDialog hd = new HTMLDialog(title, msg); + if (isMacro() && hd.escapePressed()) + throw new RuntimeException(Macro.MACRO_CANCELED); + } else { + MessageDialog md = new MessageDialog(ij, title, msg); + if (isMacro() && md.escapePressed()) + throw new RuntimeException(Macro.MACRO_CANCELED); + } + IJ.wait(25); // fix GenericDialog non-editable text field + } else + System.out.println(msg); + } + + /** Displays a message in a dialog box titled "ImageJ". If a + macro or JavaScript is running, it is aborted. Writes to the + Java console if the ImageJ window is not present.*/ + public static void error(String msg) { + error(null, msg); + if (Thread.currentThread().getName().endsWith("JavaScript")) + throw new RuntimeException(Macro.MACRO_CANCELED); + else + Macro.abort(); + } + + /** Displays a message in a dialog box with the specified title. If a + macro or JavaScript is running, it is aborted. Writes to the + Java console if the ImageJ window is not present. */ + public static void error(String title, String msg) { + if (macroInterpreter!=null) { + macroInterpreter.abort(msg); + macroInterpreter = null; + return; + } + if (msg!=null && msg.endsWith(Macro.MACRO_CANCELED)) + return; + String title2 = title!=null?title:"ImageJ"; + boolean abortMacro = title!=null; + lastErrorMessage = msg; + if (redirectErrorMessages) { + IJ.log(title2 + ": " + msg); + if (abortMacro && (title.contains("Open")||title.contains("Reader"))) + abortMacro = false; + } else + showMessage(title2, msg); + redirectErrorMessages = false; + if (abortMacro) + Macro.abort(); + } + + /** Aborts any currently running JavaScript, or use IJ.error(string) + to abort a JavaScript with a message. */ + public static void exit() { + if (Thread.currentThread().getName().endsWith("JavaScript")) + throw new RuntimeException(Macro.MACRO_CANCELED); + } + + /** + * Returns the last error message written by IJ.error() or null if there + * was no error since the last time this method was called. + * @see #error(String) + */ + public static String getErrorMessage() { + String msg = lastErrorMessage; + lastErrorMessage = null; + return msg; + } + + /** Displays a message in a dialog box with the specified title. + Returns false if the user pressed "Cancel". */ + public static boolean showMessageWithCancel(String title, String msg) { + GenericDialog gd = new GenericDialog(title); + gd.addMessage(msg); + gd.showDialog(); + return !gd.wasCanceled(); + } + + public static final int CANCELED = Integer.MIN_VALUE; + + /** Allows the user to enter a number in a dialog box. Returns the + value IJ.CANCELED (-2,147,483,648) if the user cancels the dialog box. + Returns 'defaultValue' if the user enters an invalid number. */ + public static double getNumber(String prompt, double defaultValue) { + GenericDialog gd = new GenericDialog(""); + int decimalPlaces = (int)defaultValue==defaultValue?0:2; + gd.addNumericField(prompt, defaultValue, decimalPlaces); + gd.showDialog(); + if (gd.wasCanceled()) + return CANCELED; + double v = gd.getNextNumber(); + if (gd.invalidNumber()) + return defaultValue; + else + return v; + } + + /** Allows the user to enter a string in a dialog box. Returns + "" if the user cancels the dialog box. */ + public static String getString(String prompt, String defaultString) { + GenericDialog gd = new GenericDialog(""); + gd.addStringField(prompt, defaultString, 20); + gd.showDialog(); + if (gd.wasCanceled()) + return ""; + return gd.getNextString(); + } + + /**Delays 'msecs' milliseconds.*/ + public static void wait(int msecs) { + try {Thread.sleep(msecs);} + catch (InterruptedException e) { } + } + + /** Emits an audio beep. */ + public static void beep() { + Toolkit.getDefaultToolkit().beep(); + } + + /** Returns a string something like "64K of 256MB (25%)" + * that shows how much of the available memory is in use. + * This is the string displayed when the user clicks in the + * status bar. + */ + public static String freeMemory() { + long inUse = currentMemory(); + String inUseStr = inUse<10000*1024?inUse/1024L+"K":inUse/1048576L+"MB"; + String maxStr=""; + long max = maxMemory(); + if (max>0L) { + double percent = inUse*100/max; + maxStr = " of "+max/1048576L+"MB ("+(percent<1.0?"<1":d2s(percent,0)) + "%)"; + } + return inUseStr + maxStr; + } + + /** Returns the amount of memory currently being used by ImageJ. */ + public static long currentMemory() { + long freeMem = Runtime.getRuntime().freeMemory(); + long totMem = Runtime.getRuntime().totalMemory(); + return totMem-freeMem; + } + + /** Returns the maximum amount of memory available to ImageJ or + zero if ImageJ is unable to determine this limit. */ + public static long maxMemory() { + if (maxMemory==0L) { + Memory mem = new Memory(); + maxMemory = mem.getMemorySetting(); + if (maxMemory==0L) maxMemory = mem.maxMemory(); + } + return maxMemory; + } + + public static void showTime(ImagePlus imp, long start, String str) { + showTime(imp, start, str, 1); + } + + public static void showTime(ImagePlus imp, long start, String str, int nslices) { + if (Interpreter.isBatchMode()) + return; + double seconds = (System.currentTimeMillis()-start)/1000.0; + if (seconds<=0.5 && macroRunning()) + return; + double pixels = (double)imp.getWidth() * imp.getHeight(); + double rate = pixels*nslices/seconds; + String str2; + if (rate>1000000000.0) + str2 = ""; + else if (rate<1000000.0) + str2 = ", "+d2s(rate,0)+" pixels/second"; + else + str2 = ", "+d2s(rate/1000000.0,1)+" million pixels/second"; + showStatus(str+seconds+" seconds"+str2); + } + + /** Experimental */ + public static String time(ImagePlus imp, long startNanoTime) { + double planes = imp.getStackSize(); + double seconds = (System.nanoTime()-startNanoTime)/1000000000.0; + double mpixels = imp.getWidth()*imp.getHeight()*planes/1000000.0; + String time = seconds<1.0?d2s(seconds*1000.0,0)+" ms":d2s(seconds,1)+" seconds"; + return time+", "+d2s(mpixels/seconds,1)+" million pixels/second"; + } + + /** Converts a number to a formatted string using + 2 digits to the right of the decimal point. */ + public static String d2s(double n) { + return d2s(n, 2); + } + + /** Converts a number to a rounded formatted string. + The 'decimalPlaces' argument specifies the number of + digits to the right of the decimal point (0-9). Uses + scientific notation if 'decimalPlaces is negative. */ + public static String d2s(double n, int decimalPlaces) { + if (Double.isNaN(n)||Double.isInfinite(n)) + return ""+n; + if (n==Float.MAX_VALUE) // divide by 0 in FloatProcessor + return "3.4e38"; + double np = n; + if (n<0.0) np = -n; + if (decimalPlaces<0) synchronized(IJ.class) { + decimalPlaces = -decimalPlaces; + if (decimalPlaces>9) decimalPlaces=9; + if (sf==null) { + if (dfs==null) + dfs = new DecimalFormatSymbols(Locale.US); + sf = new DecimalFormat[10]; + sf[1] = new DecimalFormat("0.0E0",dfs); + sf[2] = new DecimalFormat("0.00E0",dfs); + sf[3] = new DecimalFormat("0.000E0",dfs); + sf[4] = new DecimalFormat("0.0000E0",dfs); + sf[5] = new DecimalFormat("0.00000E0",dfs); + sf[6] = new DecimalFormat("0.000000E0",dfs); + sf[7] = new DecimalFormat("0.0000000E0",dfs); + sf[8] = new DecimalFormat("0.00000000E0",dfs); + sf[9] = new DecimalFormat("0.000000000E0",dfs); + } + return sf[decimalPlaces].format(n); // use scientific notation + } + if (decimalPlaces<0) decimalPlaces = 0; + if (decimalPlaces>9) decimalPlaces = 9; + return df[decimalPlaces].format(n); + } + + /** Converts a number to a rounded formatted string. + * The 'significantDigits' argument specifies the minimum number + * of significant digits, which is also the preferred number of + * digits behind the decimal. Fewer decimals are shown if the + * number would have more than 'maxDigits'. + * Exponential notation is used if more than 'maxDigits' would be needed. + */ + public static String d2s(double x, int significantDigits, int maxDigits) { + double log10 = Math.log10(Math.abs(x)); + double roundErrorAtMax = 0.223*Math.pow(10, -maxDigits); + int magnitude = (int)Math.ceil(log10+roundErrorAtMax); + int decimals = x==0 ? 0 : maxDigits - magnitude; + if (decimals<0 || magnitudesignificantDigits) + decimals = Math.max(significantDigits, decimals-maxDigits+significantDigits); + return IJ.d2s(x, decimals); + } + } + + /** Pad 'n' with leading zeros to the specified number of digits. */ + public static String pad(int n, int digits) { + String str = ""+n; + while (str.length()= 6; + } + + /** Returns true if ImageJ is running on a Java 1.7 or greater JVM. */ + public static boolean isJava17() { + return javaVersion >= 7; + } + + /** Returns true if ImageJ is running on a Java 1.8 or greater JVM. */ + public static boolean isJava18() { + return javaVersion >= 8; + } + + /** Returns true if ImageJ is running on a Java 1.9 or greater JVM. */ + public static boolean isJava19() { + return javaVersion >= 9; + } + + /** Returns true if ImageJ is running on Linux. */ + public static boolean isLinux() { + return isLinux; + } + + /** Obsolete; always returns false. */ + public static boolean isVista() { + return false; + } + + /** Returns true if ImageJ is running a 64-bit version of Java. */ + public static boolean is64Bit() { + if (osarch==null) + osarch = System.getProperty("os.arch"); + return osarch!=null && osarch.indexOf("64")!=-1; + } + + /** Displays an error message and returns true if the + ImageJ version is less than the one specified. */ + public static boolean versionLessThan(String version) { + boolean lessThan = ImageJ.VERSION.compareTo(version)<0; + if (lessThan) + error("This plugin or macro requires ImageJ "+version+" or later. Use\nHelp>Update ImageJ to upgrade to the latest version."); + return lessThan; + } + + /** Displays a "Process all images?" dialog. Returns + 'flags'+PlugInFilter.DOES_STACKS if the user selects "Yes", + 'flags' if the user selects "No" and PlugInFilter.DONE + if the user selects "Cancel". + */ + public static int setupDialog(ImagePlus imp, int flags) { + if (imp==null || (ij!=null&&ij.hotkey)) { + if (ij!=null) ij.hotkey=false; + return flags; + } + int stackSize = imp.getStackSize(); + if (stackSize>1) { + String macroOptions = Macro.getOptions(); + if (imp.isComposite() && ((CompositeImage)imp).getMode()==IJ.COMPOSITE) { + if (macroOptions==null || !macroOptions.contains("slice")) + return flags | PlugInFilter.DOES_STACKS; + } + if (macroOptions!=null) { + if (macroOptions.indexOf("stack ")>=0) + return flags | PlugInFilter.DOES_STACKS; + else + return flags; + } + if (hideProcessStackDialog) + return flags; + String note = ((flags&PlugInFilter.NO_CHANGES)==0)?" There is\nno Undo if you select \"Yes\".":""; + YesNoCancelDialog d = new YesNoCancelDialog(getInstance(), + "Process Stack?", "Process all "+stackSize+" images?"+note); + if (d.cancelPressed()) + return PlugInFilter.DONE; + else if (d.yesPressed()) { + if (imp.getStack().isVirtual() && ((flags&PlugInFilter.NO_CHANGES)==0)) { + int size = (stackSize*imp.getWidth()*imp.getHeight()*imp.getBytesPerPixel()+524288)/1048576; + String msg = + "Use the Process>Batch>Virtual Stack command\n"+ + "to process a virtual stack or convert it into a\n"+ + "normal stack using Image>Duplicate, which\n"+ + "will require "+size+"MB of additional memory."; + error(msg); + return PlugInFilter.DONE; + } + if (IJ.recording()) + Recorder.recordOption("stack"); + return flags | PlugInFilter.DOES_STACKS; + } + if (IJ.recording()) + Recorder.recordOption("slice"); + } + return flags; + } + + /** Creates a rectangular selection. Removes any existing + selection if width or height are less than 1. */ + public static void makeRectangle(int x, int y, int width, int height) { + if (width<=0 || height<0) + getImage().deleteRoi(); + else { + ImagePlus img = getImage(); + if (Interpreter.isBatchMode()) + img.setRoi(new Roi(x,y,width,height), false); + else + img.setRoi(x, y, width, height); + } + } + + /** Creates a subpixel resolution rectangular selection. */ + public static void makeRectangle(double x, double y, double width, double height) { + if (width<=0 || height<0) + getImage().deleteRoi(); + else + getImage().setRoi(new Roi(x,y,width,height), !Interpreter.isBatchMode()); + } + + /** Creates an oval selection. Removes any existing + selection if width or height are less than 1. */ + public static void makeOval(int x, int y, int width, int height) { + if (width<=0 || height<0) + getImage().deleteRoi(); + else { + ImagePlus img = getImage(); + img.setRoi(new OvalRoi(x, y, width, height)); + } + } + + /** Creates an subpixel resolution oval selection. */ + public static void makeOval(double x, double y, double width, double height) { + if (width<=0 || height<0) + getImage().deleteRoi(); + else + getImage().setRoi(new OvalRoi(x, y, width, height)); + } + + /** Creates a straight line selection. */ + public static void makeLine(int x1, int y1, int x2, int y2) { + getImage().setRoi(new Line(x1, y1, x2, y2)); + } + + /** Creates a straight line selection using floating point coordinates. */ + public static void makeLine(double x1, double y1, double x2, double y2) { + getImage().setRoi(new Line(x1, y1, x2, y2)); + } + + /** Creates a point selection using integer coordinates.. */ + public static void makePoint(int x, int y) { + ImagePlus img = getImage(); + Roi roi = img.getRoi(); + if (shiftKeyDown() && roi!=null && roi.getType()==Roi.POINT) { + ((PointRoi)roi).addUserPoint(null, x, y); + IJ.setKeyUp(KeyEvent.VK_SHIFT); + } else if (altKeyDown() && roi!=null && roi.getType()==Roi.POINT) { + ((PolygonRoi)roi).deleteHandle(x, y); + IJ.setKeyUp(KeyEvent.VK_ALT); + } else + img.setRoi(new PointRoi(x, y)); + } + + /** Creates a point selection using floating point coordinates. */ + public static void makePoint(double x, double y) { + ImagePlus img = getImage(); + Roi roi = img.getRoi(); + if (shiftKeyDown() && roi!=null && roi.getType()==Roi.POINT) { + ((PointRoi)roi).addUserPoint(null, x, y); + IJ.setKeyUp(KeyEvent.VK_SHIFT); + } else if (altKeyDown() && roi!=null && roi.getType()==Roi.POINT) { + ((PolygonRoi)roi).deleteHandle(x, y); + IJ.setKeyUp(KeyEvent.VK_ALT); + } else + img.setRoi(new PointRoi(x, y)); + } + + /** Creates an Roi. */ + public static Roi Roi(double x, double y, double width, double height) { + return new Roi(x, y, width, height); + } + + /** Creates an OvalRoi. */ + public static OvalRoi OvalRoi(double x, double y, double width, double height) { + return new OvalRoi(x, y, width, height); + } + + /** Sets the display range (minimum and maximum displayed pixel values) of the current image. */ + public static void setMinAndMax(double min, double max) { + setMinAndMax(getImage(), min, max, 7); + } + + /** Sets the display range (minimum and maximum displayed pixel values) of the specified image. */ + public static void setMinAndMax(ImagePlus img, double min, double max) { + setMinAndMax(img, min, max, 7); + } + + /** Sets the minimum and maximum displayed pixel values on the specified RGB + channels, where 4=red, 2=green and 1=blue. */ + public static void setMinAndMax(double min, double max, int channels) { + setMinAndMax(getImage(), min, max, channels); + } + + private static void setMinAndMax(ImagePlus img, double min, double max, int channels) { + Calibration cal = img.getCalibration(); + min = cal.getRawValue(min); + max = cal.getRawValue(max); + if (channels==7) + img.setDisplayRange(min, max); + else + img.setDisplayRange(min, max, channels); + img.updateAndDraw(); + } + + /** Resets the minimum and maximum displayed pixel values of the + current image to be the same as the min and max pixel values. */ + public static void resetMinAndMax() { + resetMinAndMax(getImage()); + } + + /** Resets the minimum and maximum displayed pixel values of the + specified image to be the same as the min and max pixel values. */ + public static void resetMinAndMax(ImagePlus img) { + img.resetDisplayRange(); + img.updateAndDraw(); + } + + /** Sets the lower and upper threshold levels and displays the image + using red to highlight thresholded pixels. May not work correctly on + 16 and 32 bit images unless the display range has been reset using IJ.resetMinAndMax(). + */ + public static void setThreshold(double lowerThreshold, double upperThresold) { + setThreshold(lowerThreshold, upperThresold, null); + } + + /** Sets the lower and upper threshold levels and displays the image using + the specified displayMode ("Red", "Black & White", "Over/Under" or "No Update"). */ + public static void setThreshold(double lowerThreshold, double upperThreshold, String displayMode) { + setThreshold(getImage(), lowerThreshold, upperThreshold, displayMode); + } + + /** Sets the lower and upper threshold levels of the specified image. */ + public static void setThreshold(ImagePlus img, double lowerThreshold, double upperThreshold) { + setThreshold(img, lowerThreshold, upperThreshold, "Red"); + } + + /** Sets the lower and upper threshold levels of the specified image and updates the display using + the specified displayMode ("Red", "Black & White", "Over/Under" or "No Update"). + With calibrated images, 'lowerThreshold' and 'upperThreshold' must be density calibrated values. + Use setRawThreshold() to set the threshold using raw (uncalibrated) values. */ + public static void setThreshold(ImagePlus img, double lowerThreshold, double upperThreshold, String displayMode) { + Calibration cal = img.getCalibration(); + if (displayMode==null || !displayMode.contains("raw")) { + lowerThreshold = cal.getRawValue(lowerThreshold); + upperThreshold = cal.getRawValue(upperThreshold); + } + setRawThreshold(img, lowerThreshold, upperThreshold, displayMode); + } + + /** This is a version of setThreshold() that uses raw (uncalibrated) + * values in the range 0-255 for 8-bit images and 0-65535 for 16-bit + * images and the "Red" LUT display mode. + */ + public static void setRawThreshold(ImagePlus img, double lowerThreshold, double upperThreshold) { + setRawThreshold(img, lowerThreshold, upperThreshold, null); + } + + /** This is a version of setThreshold() that always uses raw (uncalibrated) values + * in the range 0-255 for 8-bit images and 0-65535 for 16-bit images. + */ + public static void setRawThreshold(ImagePlus img, double lowerThreshold, double upperThreshold, String displayMode) { + int mode = ImageProcessor.RED_LUT; + if (displayMode!=null) { + displayMode = displayMode.toLowerCase(Locale.US); + if (displayMode.contains("black")) + mode = ImageProcessor.BLACK_AND_WHITE_LUT; + else if (displayMode.contains("over")) + mode = ImageProcessor.OVER_UNDER_LUT; + else if (displayMode.contains("no")) + mode = ImageProcessor.NO_LUT_UPDATE; + } + img.getProcessor().setThreshold(lowerThreshold, upperThreshold, mode); + if (mode!=ImageProcessor.NO_LUT_UPDATE && img.getWindow()!=null) { + img.getProcessor().setLutAnimation(true); + img.updateAndDraw(); + ThresholdAdjuster.update(); + } + } + + /** Replaced by ImagePlus.setAutoThreshold(). */ + public static void setAutoThreshold(ImagePlus imp, String method) { + ImageProcessor ip = imp.getProcessor(); + if (ip instanceof ColorProcessor) + throw new IllegalArgumentException("Non-RGB image required"); + ip.setRoi(imp.getRoi()); + if (method!=null) { + try { + if (method.indexOf("stack")!=-1) + setStackThreshold(imp, ip, method); + else + ip.setAutoThreshold(method); + } catch (Exception e) { + log(e.getMessage()); + } + } else + ip.setAutoThreshold(ImageProcessor.ISODATA2, ImageProcessor.RED_LUT); + ThresholdAdjuster.setMethod(method); + imp.updateAndDraw(); + } + + private static void setStackThreshold(ImagePlus imp, ImageProcessor ip, String method) { + boolean darkBackground = method.contains("dark"); + boolean histo16 = imp.getBitDepth()==16 && method.contains("16"); + //IJ.log("setStackThreshold: "+histo16+" "+method); + boolean addOne = !method.contains("16"); + int measurements = Analyzer.getMeasurements(); + Analyzer.setMeasurements(Measurements.AREA+Measurements.MIN_MAX); + ImageStatistics stats = new StackStatistics(imp); + Analyzer.setMeasurements(measurements); + AutoThresholder thresholder = new AutoThresholder(); + double min=0.0, max=255.0; + if (imp.getBitDepth()!=8) { + min = stats.min; + max = stats.max; + } + int[] histogram = stats.histogram; + if (histo16) + histogram = stats.histogram16; + int threshold = thresholder.getThreshold(method, histogram); + double lower, upper; + double tmax = 255.0; + if (histogram.length>256) + tmax = 65535.0; + if (darkBackground) { + if (ip.isInvertedLut()) + {lower=0.0; upper=threshold;} + else + {lower=threshold+(addOne?1:0); upper=tmax;} + } else { + if (ip.isInvertedLut()) + {lower=threshold+(addOne?1:0); upper=tmax;} + else + {lower=0.0; upper=threshold;} + } + if (!histo16) { + if (lower>255) lower = 255; + if (max>min) { + lower = min + (lower/255.0)*(max-min); + upper = min + (upper/255.0)*(max-min); + } else + lower = upper = min; + ip.setMinAndMax(min, max); + } + ip.setThreshold(lower, upper, ImageProcessor.RED_LUT); + imp.updateAndDraw(); + } + + /** Disables thresholding on the current image. */ + public static void resetThreshold() { + resetThreshold(getImage()); + } + + /** Disables thresholding on the specified image. */ + public static void resetThreshold(ImagePlus img) { + ImageProcessor ip = img.getProcessor(); + ip.resetThreshold(); + ip.setLutAnimation(true); + img.updateAndDraw(); + ThresholdAdjuster.update(); + } + + /** For IDs less than zero, activates the image with the specified ID. + For IDs greater than zero, activates the Nth image. */ + public static void selectWindow(int id) { + if (id>0) + id = WindowManager.getNthImageID(id); + ImagePlus imp = WindowManager.getImage(id); + if (imp==null) + error("Macro Error", "Image "+id+" not found or no images are open."); + if (Interpreter.isBatchMode()) { + ImagePlus impT = WindowManager.getTempCurrentImage(); + ImagePlus impC = WindowManager.getCurrentImage(); + if (impC!=null && impC!=imp && impT!=null) + impC.saveRoi(); + WindowManager.setTempCurrentImage(imp); + Interpreter.activateImage(imp); + WindowManager.setWindow(null); + } else { + if (imp==null) + return; + ImageWindow win = imp.getWindow(); + if (win!=null) { + win.toFront(); + win.setState(Frame.NORMAL); + WindowManager.setWindow(win); + } + long start = System.currentTimeMillis(); + // timeout after 1 second unless current thread is event dispatch thread + String thread = Thread.currentThread().getName(); + int timeout = thread!=null&&thread.indexOf("EventQueue")!=-1?0:1000; + if (IJ.isMacOSX() && IJ.isJava18() && timeout>0) + timeout = 250; //work around OS X/Java 8 window activation bug + while (true) { + wait(10); + imp = WindowManager.getCurrentImage(); + if (imp!=null && imp.getID()==id) + return; // specified image is now active + if ((System.currentTimeMillis()-start)>timeout && win!=null) { + WindowManager.setCurrentWindow(win); + return; + } + } + } + } + + /** Activates the window with the specified title. */ + public static void selectWindow(String title) { + if (title.equals("ImageJ")&&ij!=null) { + ij.toFront(); + return; + } + long start = System.currentTimeMillis(); + while (System.currentTimeMillis()-start<3000) { // 3 sec timeout + Window win = WindowManager.getWindow(title); + if (win!=null && !(win instanceof ImageWindow)) { + selectWindow(win); + return; + } + int[] wList = WindowManager.getIDList(); + int len = wList!=null?wList.length:0; + for (int i=0; i1000) { + WindowManager.setWindow(win); + return; // 1 second timeout + } + } + } + + /** Sets the foreground color. */ + public static void setForegroundColor(int red, int green, int blue) { + setColor(red, green, blue, true); + } + + /** Sets the background color. */ + public static void setBackgroundColor(int red, int green, int blue) { + setColor(red, green, blue, false); + } + + static void setColor(int red, int green, int blue, boolean foreground) { + Color c = Colors.toColor(red, green, blue); + if (foreground) { + Toolbar.setForegroundColor(c); + ImagePlus img = WindowManager.getCurrentImage(); + if (img!=null) + img.getProcessor().setColor(c); + } else + Toolbar.setBackgroundColor(c); + } + + /** Switches to the specified tool, where id = Toolbar.RECTANGLE (0), + Toolbar.OVAL (1), etc. */ + public static void setTool(int id) { + Toolbar.getInstance().setTool(id); + } + + /** Switches to the specified tool, where 'name' is "rect", "elliptical", + "brush", etc. Returns 'false' if the name is not recognized. */ + public static boolean setTool(String name) { + return Toolbar.getInstance().setTool(name); + } + + /** Returns the name of the current tool. */ + public static String getToolName() { + return Toolbar.getToolName(); + } + + /** Equivalent to clicking on the current image at (x,y) with the + wand tool. Returns the number of points in the resulting ROI. */ + public static int doWand(int x, int y) { + return doWand(getImage(), x, y, 0, null); + } + + /** Traces the boundary of the area with pixel values within + * 'tolerance' of the value of the pixel at the starting location. + * 'tolerance' is in uncalibrated units. + * 'mode' can be "4-connected", "8-connected" or "Legacy". + * "Legacy" is for compatibility with previous versions of ImageJ; + * it is ignored if 'tolerance' > 0. + */ + public static int doWand(int x, int y, double tolerance, String mode) { + return doWand(getImage(), x, y, tolerance, mode); + } + + /** This version of doWand adds an ImagePlus argument. */ + public static int doWand(ImagePlus img, int x, int y, double tolerance, String mode) { + ImageProcessor ip = img.getProcessor(); + if ((img.getType()==ImagePlus.GRAY32) && Double.isNaN(ip.getPixelValue(x,y))) + return 0; + int imode = Wand.LEGACY_MODE; + boolean smooth = false; + if (mode!=null) { + if (mode.startsWith("4")) + imode = Wand.FOUR_CONNECTED; + else if (mode.startsWith("8")) + imode = Wand.EIGHT_CONNECTED; + smooth = mode.contains("smooth"); + + } + Wand w = new Wand(ip); + if (!ip.isThreshold() || (ip.getLutUpdateMode()==ImageProcessor.NO_LUT_UPDATE&& tolerance>0.0)) { + w.autoOutline(x, y, tolerance, imode); + smooth = false; + } else { + w.autoOutline(x, y, ip.getMinThreshold(), ip.getMaxThreshold(), imode); + } + if (w.npoints>0) { + Roi previousRoi = img.getRoi(); + Roi roi = new PolygonRoi(w.xpoints, w.ypoints, w.npoints, Roi.TRACED_ROI); + img.deleteRoi(); + img.setRoi(roi); + if (previousRoi!=null) + roi.update(shiftKeyDown(), altKeyDown()); // add/subtract ROI to previous one if shift/alt key down + Roi roi2 = img.getRoi(); + if (smooth && roi2!=null && roi2.getType()==Roi.TRACED_ROI) { + Rectangle bounds = roi2.getBounds(); + if (bounds.width>1 && bounds.height>1) { + if (smoothMacro==null) + smoothMacro = BatchProcessor.openMacroFromJar("SmoothWandTool.txt"); + if (EventQueue.isDispatchThread()) + new MacroRunner(smoothMacro); // run on separate thread + else + Macro.eval(smoothMacro); + } + } + } + return w.npoints; + } + + /** Sets the transfer mode used by the Edit/Paste command, where mode is "Copy", "Blend", "Average", "Difference", + "Transparent", "Transparent2", "AND", "OR", "XOR", "Add", "Subtract", "Multiply", or "Divide". */ + public static void setPasteMode(String mode) { + Roi.setPasteMode(stringToPasteMode(mode)); + } + + public static int stringToPasteMode(String mode) { + if (mode==null) + return Blitter.COPY; + mode = mode.toLowerCase(Locale.US); + int m = Blitter.COPY; + if (mode.startsWith("ble") || mode.startsWith("ave")) + m = Blitter.AVERAGE; + else if (mode.startsWith("diff")) + m = Blitter.DIFFERENCE; + else if (mode.indexOf("zero")!=-1) + m = Blitter.COPY_ZERO_TRANSPARENT; + else if (mode.startsWith("tran")) + m = Blitter.COPY_TRANSPARENT; + else if (mode.startsWith("and")) + m = Blitter.AND; + else if (mode.startsWith("or")) + m = Blitter.OR; + else if (mode.startsWith("xor")) + m = Blitter.XOR; + else if (mode.startsWith("sub")) + m = Blitter.SUBTRACT; + else if (mode.startsWith("add")) + m = Blitter.ADD; + else if (mode.startsWith("div")) + m = Blitter.DIVIDE; + else if (mode.startsWith("mul")) + m = Blitter.MULTIPLY; + else if (mode.startsWith("min")) + m = Blitter.MIN; + else if (mode.startsWith("max")) + m = Blitter.MAX; + return m; + } + + /** Returns a reference to the active image, or displays an error + message and aborts the plugin or macro if no images are open. */ + public static ImagePlus getImage() { + ImagePlus img = WindowManager.getCurrentImage(); + if (img==null) { + IJ.noImage(); + if (ij==null) + System.exit(0); + else + abort(); + } + return img; + } + + /**The macro interpreter uses this method to call getImage().*/ + public static ImagePlus getImage(Interpreter interpreter) { + macroInterpreter = interpreter; + ImagePlus imp = getImage(); + macroInterpreter = null; + return imp; + } + + /** Returns the active image or stack slice as an ImageProcessor, or displays + an error message and aborts the plugin or macro if no images are open. */ + public static ImageProcessor getProcessor() { + ImagePlus imp = IJ.getImage(); + return imp.getProcessor(); + } + + /** Switches to the specified stack slice, where 1<='slice'<=stack-size. */ + public static void setSlice(int slice) { + getImage().setSlice(slice); + } + + /** Returns the ImageJ version number as a string. */ + public static String getVersion() { + return ImageJ.VERSION; + } + + /** Returns the ImageJ version and build number as a String, for + example "1.46n05", or 1.46n99 if there is no build number. */ + public static String getFullVersion() { + String build = ImageJ.BUILD; + if (build.length()==0) + build = "99"; + else if (build.length()==1) + build = "0" + build; + return ImageJ.VERSION+build; + } + + /** Returns the path to the specified directory if title is + "home" ("user.home"), "downloads", "startup", "imagej" (ImageJ directory), + "plugins", "macros", "luts", "temp", "current", "default", + "image" (directory active image was loaded from), "file" + (directory most recently used to open or save a file), "cwd" + (current working directory) or "preferences" (location of + "IJ_Prefs.txt" file), otherwise displays a dialog and + returns the path to the directory selected by the user. Returns + null if the specified directory is not found or the user cancels the + dialog box. Also aborts the macro if the user cancels the + dialog box.*/ + public static String getDirectory(String title) { + String dir = null; + String title2 = title.toLowerCase(Locale.US); + if (title2.equals("plugins")) + dir = Menus.getPlugInsPath(); + else if (title2.equals("macros")) + dir = Menus.getMacrosPath(); + else if (title2.equals("luts")) { + String ijdir = Prefs.getImageJDir(); + if (ijdir!=null) + dir = ijdir + "luts" + File.separator; + else + dir = null; + } else if (title2.equals("home")) + dir = System.getProperty("user.home"); + else if (title2.equals("downloads")) + dir = System.getProperty("user.home")+File.separator+"Downloads"; + else if (title2.equals("startup")) + dir = Prefs.getImageJDir(); + else if (title2.equals("imagej")) + dir = Prefs.getImageJDir(); + else if (title2.equals("current") || title2.equals("default")) + dir = OpenDialog.getDefaultDirectory(); + else if (title2.equals("preferences")) + dir = Prefs.getPrefsDir(); + else if (title2.equals("temp")) { + dir = System.getProperty("java.io.tmpdir"); + if (isMacintosh()) dir = "/tmp/"; + } else if (title2.equals("image")) { + ImagePlus imp = WindowManager.getCurrentImage(); + FileInfo fi = imp!=null?imp.getOriginalFileInfo():null; + if (fi!=null && fi.directory!=null) { + dir = fi.directory; + } else + dir = null; + } else if (title2.equals("file")) + dir = OpenDialog.getLastDirectory(); + else if (title2.equals("cwd")) + dir = System.getProperty("user.dir"); + else { + String defaultDir = OpenDialog.getDefaultDirectory(); + DirectoryChooser dc = new DirectoryChooser(title); + dir = dc.getDirectory(); + OpenDialog.setDefaultDirectory(defaultDir); + if (dir==null) Macro.abort(); + } + dir = addSeparator(dir); + return dir; + } + + public static String addSeparator(String path) { + if (path==null) + return null; + if (path.length()>0 && !(path.endsWith(File.separator)||path.endsWith("/"))) { + if (IJ.isWindows()&&path.contains(File.separator)) + path += File.separator; + else + path += "/"; + } + return path; + } + + /** Alias for getDirectory(). */ + public static String getDir(String title) { + return getDirectory(title); + } + + + /** Displays an open file dialog and returns the path to the + choosen file, or returns null if the dialog is canceled. */ + public static String getFilePath(String dialogTitle) { + OpenDialog od = new OpenDialog(dialogTitle); + return od.getPath(); + } + + /** Displays a file open dialog box and then opens the tiff, dicom, + fits, pgm, jpeg, bmp, gif, lut, roi, or text file selected by + the user. Displays an error message if the selected file is not + in one of the supported formats, or if it is not found. */ + public static void open() { + open(null); + } + + /** Opens and displays a tiff, dicom, fits, pgm, jpeg, bmp, gif, lut, + roi, or text file. Displays an error message if the specified file + is not in one of the supported formats, or if it is not found. + With 1.41k or later, opens images specified by a URL. + */ + public static void open(String path) { + if (ij==null && Menus.getCommands()==null) + init(); + Opener o = new Opener(); + macroRunning = true; + if (path==null || path.equals("")) + o.open(); + else + o.open(path); + macroRunning = false; + } + + /** Opens and displays the nth image in the specified tiff stack. */ + public static void open(String path, int n) { + if (ij==null && Menus.getCommands()==null) + init(); + ImagePlus imp = openImage(path, n); + if (imp!=null) imp.show(); + } + + /** Opens the specified file as a tiff, bmp, dicom, fits, pgm, gif, jpeg + * or text image and returns an ImagePlus object if successful. + * Calls HandleExtraFileTypes plugin if the file type is not recognised. + * Displays a file open dialog if 'path' is null or an empty string. + * Note that 'path' can also be a URL. Some reader plugins, including + * the Bio-Formats plugin, display the image and return null. + * Use IJ.open() to display a file open dialog box. + * @see ij.io.Opener#openUsingBioFormats(String) + */ + public static ImagePlus openImage(String path) { + macroRunning = true; + ImagePlus imp = (new Opener()).openImage(path); + macroRunning = false; + return imp; + } + + /** Opens the nth image of the specified tiff stack. */ + public static ImagePlus openImage(String path, int n) { + Opener opener = new Opener(); + opener.doNotUseBioFormats(); + return opener.openImage(path, n); + } + + /** Opens the specified tiff file as a virtual stack. */ + public static ImagePlus openVirtual(String path) { + return FileInfoVirtualStack.openVirtual(path); + } + + /** Opens an image using a file open dialog and returns it as an ImagePlus object. */ + public static ImagePlus openImage() { + return openImage(null); + } + + /** Opens a URL and returns the contents as a string. + Returns "" if there an error, including + host or file not found. */ + public static String openUrlAsString(String url) { + //if (!trustManagerCreated && url.contains("nih.gov")) trustAllCerts(); + url = Opener.updateUrl(url); + if (debugMode) log("OpenUrlAsString: "+url); + StringBuffer sb = null; + url = url.replaceAll(" ", "%20"); + try { + //if (url.contains("nih.gov")) addRootCA(); + URL u = new URL(url); + URLConnection uc = u.openConnection(); + long len = uc.getContentLength(); + if (len>5242880L) + return ""; + InputStream in = u.openStream(); + BufferedReader br = new BufferedReader(new InputStreamReader(in,"UTF-8")); + sb = new StringBuffer() ; + String line; + while ((line=br.readLine()) != null) + sb.append (line + "\n"); + in.close (); + } catch (Exception e) { + return(""); + } + if (sb!=null) + return new String(sb); + else + return ""; + } + + /** Saves the current image, lookup table, selection or text window to the specified file path. + The path must end in ".tif", ".jpg", ".gif", ".zip", ".raw", ".avi", ".bmp", ".fits", ".pgm", ".png", ".lut", ".roi" or ".txt". */ + public static void save(String path) { + save(null, path); + } + + /** Saves the specified image, lookup table or selection to the specified file path. + The file path should end with ".tif", ".jpg", ".gif", ".zip", ".raw", ".avi", ".bmp", + ".fits", ".pgm", ".png", ".lut", ".roi" or ".txt". The specified image is saved in + TIFF format if there is no extension. */ + public static void save(ImagePlus imp, String path) { + ImagePlus imp2 = imp; + if (imp2==null) + imp2 = WindowManager.getCurrentImage(); + int dotLoc = path.lastIndexOf('.'); + if (dotLoc==-1 && imp2!=null) { + path = path + ".tif"; // save as TIFF if file name does not have an extension + dotLoc = path.lastIndexOf('.'); + } + if (dotLoc!=-1) { + String title = imp2!=null?imp2.getTitle():null; + saveAs(imp, path.substring(dotLoc+1), path); + if (title!=null) + imp2.setTitle(title); + } else + error("The file path passed to IJ.save() method or save()\nmacro function is missing the required extension.\n \n\""+path+"\""); + } + + /* Saves the active image, lookup table, selection, measurement results, selection XY + coordinates or text window to the specified file path. The format argument must be "tiff", + "jpeg", "gif", "zip", "raw", "avi", "bmp", "fits", "pgm", "png", "text image", "lut", "selection", "measurements", + "xy Coordinates" or "text". If path is null or an emply string, a file + save dialog is displayed. */ + public static void saveAs(String format, String path) { + saveAs(null, format, path); + } + + /* Saves the specified image. The format argument must be "tiff", + "jpeg", "gif", "zip", "raw", "avi", "bmp", "fits", "pgm", "png", + "text image", "lut", "selection" or "xy Coordinates". */ + public static void saveAs(ImagePlus imp, String format, String path) { + if (format==null) + return; + if (path!=null && path.length()==0) + path = null; + format = format.toLowerCase(Locale.US); + Roi roi2 = imp!=null?imp.getRoi():null; + if (roi2!=null) + roi2.endPaste(); + if (format.indexOf("tif")!=-1) { + saveAsTiff(imp, path); + return; + } else if (format.indexOf("jpeg")!=-1 || format.indexOf("jpg")!=-1) { + path = updateExtension(path, ".jpg"); + JpegWriter.save(imp, path, FileSaver.getJpegQuality()); + return; + } else if (format.indexOf("gif")!=-1) { + path = updateExtension(path, ".gif"); + GifWriter.save(imp, path); + return; + } else if (format.indexOf("text image")!=-1) { + String extension = ".txt"; + if (path!=null && (path.endsWith(".csv")||path.endsWith(".CSV"))) + extension = ".csv"; + path = updateExtension(path, extension); + format = "Text Image..."; + } else if (format.indexOf("text")!=-1 || format.indexOf("txt")!=-1) { + if (path!=null && !path.endsWith(".xls") && !path.endsWith(".csv") && !path.endsWith(".tsv")) + path = updateExtension(path, ".txt"); + format = "Text..."; + } else if (format.indexOf("zip")!=-1) { + path = updateExtension(path, ".zip"); + format = "ZIP..."; + } else if (format.indexOf("raw")!=-1) { + //path = updateExtension(path, ".raw"); + format = "Raw Data..."; + } else if (format.indexOf("avi")!=-1) { + path = updateExtension(path, ".avi"); + format = "AVI... "; + } else if (format.indexOf("bmp")!=-1) { + path = updateExtension(path, ".bmp"); + format = "BMP..."; + } else if (format.indexOf("fits")!=-1) { + path = updateExtension(path, ".fits"); + format = "FITS..."; + } else if (format.indexOf("png")!=-1) { + path = updateExtension(path, ".png"); + format = "PNG..."; + } else if (format.indexOf("pgm")!=-1) { + path = updateExtension(path, ".pgm"); + format = "PGM..."; + } else if (format.indexOf("lut")!=-1) { + path = updateExtension(path, ".lut"); + format = "LUT..."; + } else if (format.contains("results") || format.contains("measurements") || format.contains("table")) { + format = "Results..."; + } else if (format.contains("selection") || format.contains("roi")) { + path = updateExtension(path, ".roi"); + format = "Selection..."; + } else if (format.indexOf("xy")!=-1 || format.indexOf("coordinates")!=-1) { + path = updateExtension(path, ".txt"); + format = "XY Coordinates..."; + } else + error("Unsupported save() or saveAs() file format: \""+format+"\"\n \n\""+path+"\""); + if (path==null) + run(format); + else { + if (path.contains(" ")) + run(imp, format, "save=["+path+"]"); + else + run(imp, format, "save="+path); + } + } + + /** Saves the specified image in TIFF format. Displays a file save dialog + if 'path' is null or an empty string. Returns 'false' if there is an + error or if the user selects "Cancel" in the file save dialog. */ + public static boolean saveAsTiff(ImagePlus imp, String path) { + if (imp==null) + imp = getImage(); + if (path==null || path.equals("")) + return (new FileSaver(imp)).saveAsTiff(); + if (!path.endsWith(".tiff")) + path = updateExtension(path, ".tif"); + FileSaver fs = new FileSaver(imp); + boolean ok; + if (imp.getStackSize()>1) + ok = fs.saveAsTiffStack(path); + else + ok = fs.saveAsTiff(path); + if (ok) + fs.updateImagePlus(path, FileInfo.TIFF); + return ok; + } + + static String updateExtension(String path, String extension) { + if (path==null) return null; + int dotIndex = path.lastIndexOf("."); + int separatorIndex = path.lastIndexOf(File.separator); + if (dotIndex>=0 && dotIndex>separatorIndex && (path.length()-dotIndex)<=5) { + if (dotIndex+1Type should contain "8-bit", "16-bit", "32-bit" or "RGB". + In addition, it can contain "white", "black" or "ramp". Width + and height specify the width and height of the image in pixels. + Depth specifies the number of stack slices. */ + public static ImagePlus createImage(String title, String type, int width, int height, int depth) { + type = type.toLowerCase(Locale.US); + int bitDepth = 8; + if (type.contains("16")) + bitDepth = 16; + boolean signedInt = type.contains("32-bit int"); + if (type.contains("32")) + bitDepth = 32; + if (type.contains("24") || type.contains("rgb") || signedInt) + bitDepth = 24; + int options = NewImage.FILL_WHITE; + if (bitDepth==16 || bitDepth==32) + options = NewImage.FILL_BLACK; + if (type.contains("white")) + options = NewImage.FILL_WHITE; + else if (type.contains("black")) + options = NewImage.FILL_BLACK; + else if (type.contains("ramp")) + options = NewImage.FILL_RAMP; + else if (type.contains("noise") || type.contains("random")) + options = NewImage.FILL_NOISE; + options += NewImage.CHECK_AVAILABLE_MEMORY; + if (signedInt) + options += NewImage.SIGNED_INT; + return NewImage.createImage(title, width, height, depth, bitDepth, options); + } + + /** Creates a new hyperstack. + * @param title image name + * @param type "8-bit", "16-bit", "32-bit" or "RGB". May also + * contain "white" , "black" (the default), "ramp", "composite-mode", + * "color-mode", "grayscale-mode or "label". + * @param width image width in pixels + * @param height image height in pixels + * @param channels number of channels + * @param slices number of slices + * @param frames number of frames + */ + public static ImagePlus createImage(String title, String type, int width, int height, int channels, int slices, int frames) { + if (type.contains("label")) + type += "ramp"; + if (!(type.contains("white")||type.contains("ramp"))) + type += "black"; + ImagePlus imp = IJ.createImage(title, type, width, height, channels*slices*frames); + imp.setDimensions(channels, slices, frames); + int mode = IJ.COLOR; + if (type.contains("composite")) + mode = IJ.COMPOSITE; + if (type.contains("grayscale")) + mode = IJ.GRAYSCALE; + if (channels>1 && imp.getBitDepth()!=24) + imp = new CompositeImage(imp, mode); + imp.setOpenAsHyperStack(true); + if (type.contains("label")) + HyperStackMaker.labelHyperstack(imp); + return imp; + } + + /** Creates a new hyperstack. + * @param title image name + * @param width image width in pixels + * @param height image height in pixels + * @param channels number of channels + * @param slices number of slices + * @param frames number of frames + * @param bitdepth 8, 16, 32 (float) or 24 (RGB) + */ + public static ImagePlus createHyperStack(String title, int width, int height, int channels, int slices, int frames, int bitdepth) { + ImagePlus imp = createImage(title, width, height, channels*slices*frames, bitdepth); + imp.setDimensions(channels, slices, frames); + if (channels>1 && bitdepth!=24) + imp = new CompositeImage(imp, IJ.COMPOSITE); + imp.setOpenAsHyperStack(true); + return imp; + } + + /** Opens a new image. Type should contain "8-bit", "16-bit", "32-bit" or "RGB". + In addition, it can contain "white", "black" or "ramp". Width + and height specify the width and height of the image in pixels. + Depth specifies the number of stack slices. */ + public static void newImage(String title, String type, int width, int height, int depth) { + ImagePlus imp = createImage(title, type, width, height, depth); + if (imp!=null) { + macroRunning = true; + imp.show(); + macroRunning = false; + } + } + + /** Returns true if the Esc key was pressed since the + last ImageJ command started to execute or since resetEscape() was called. */ + public static boolean escapePressed() { + return escapePressed; + } + + /** This method sets the Esc key to the "up" position. + The Executer class calls this method when it runs + an ImageJ command in a separate thread. */ + public static void resetEscape() { + escapePressed = false; + } + + /** Causes IJ.error() output to be temporarily redirected to the "Log" window. */ + public static void redirectErrorMessages() { + redirectErrorMessages = true; + lastErrorMessage = null; + } + + /** Set 'true' and IJ.error() output will be temporarily redirected to the "Log" window. */ + public static void redirectErrorMessages(boolean redirect) { + redirectErrorMessages = redirect; + lastErrorMessage = null; + } + + /** Returns the state of the 'redirectErrorMessages' flag, which is set by File/Import/Image Sequence. */ + public static boolean redirectingErrorMessages() { + return redirectErrorMessages; + } + + /** Temporarily suppress "plugin not found" errors. */ + public static void suppressPluginNotFoundError() { + suppressPluginNotFoundError = true; + } + + /** Returns the class loader ImageJ uses to run plugins or the + system class loader if Menus.getPlugInsPath() returns null. */ + public static ClassLoader getClassLoader() { + if (classLoader==null) { + String pluginsDir = Menus.getPlugInsPath(); + if (pluginsDir==null) { + String home = System.getProperty("plugins.dir"); + if (home!=null) { + if (!home.endsWith(Prefs.separator)) home+=Prefs.separator; + pluginsDir = home+"plugins"+Prefs.separator; + if (!(new File(pluginsDir)).isDirectory()) + pluginsDir = home; + } + } + if (pluginsDir==null) + return IJ.class.getClassLoader(); + else { + if (Menus.jnlp) + classLoader = new PluginClassLoader(pluginsDir, true); + else + classLoader = new PluginClassLoader(pluginsDir); + } + } + return classLoader; + } + + /** Returns the size, in pixels, of the primary display. */ + public static Dimension getScreenSize() { + Rectangle bounds = GUI.getScreenBounds(); + return new Dimension(bounds.width, bounds.height); + } + + /** Returns, as an array of strings, a list of the LUTs in the + * Image/Lookup Tables menu. + * @see ij.plugin.LutLoader#getLut + * See also: Help>Examples>JavaScript/Show all LUTs + * and Image/Color/Display LUTs + */ + public static String[] getLuts() { + ArrayList list = new ArrayList(); + Hashtable commands = Menus.getCommands(); + Menu lutsMenu = Menus.getImageJMenu("Image>Lookup Tables"); + if (commands==null || lutsMenu==null) + return new String[0]; + for (int i=0; i +ImageJ is a work of the United States Government. It is in the public domain +and open source. There is no copyright. You are free to do anything you want +with this source but I like to get credit for my work and I would like you to +offer your changes to me so I can possibly add them to the "official" version. + +
+The following command line options are recognized by ImageJ:
+
+  "file-name"
+     Opens a file
+     Example 1: blobs.tif
+     Example 2: /Users/wayne/images/blobs.tif
+     Example 3: e81*.tif
+
+  -macro path [arg]
+     Runs a macro or script (JavaScript, BeanShell or Python), passing an
+     optional string argument, which the macro or script can be retrieve
+     using the getArgument() function. The macro or script is assumed to 
+     be in the ImageJ/macros folder if 'path' is not a full directory path.
+     Example 1: -macro analyze.ijm
+     Example 2: -macro script.js /Users/wayne/images/stack1
+     Example 2: -macro script.py '1.2 2.4 3.8'
+
+  -batch path [arg]
+    Runs a macro or script (JavaScript, BeanShell or Python) in
+    batch (no GUI) mode, passing an optional argument.
+    ImageJ exits when the macro finishes.
+
+  -eval "macro code"
+     Evaluates macro code
+     Example 1: -eval "print('Hello, world');"
+     Example 2: -eval "return getVersion();"
+
+  -run command
+     Runs an ImageJ menu command
+     Example: -run "About ImageJ..."
+     
+  -ijpath path
+     Specifies the path to the directory containing the plugins directory
+     Example: -ijpath /Applications/ImageJ
+
+  -port
+     Specifies the port ImageJ uses to determine if another instance is running
+     Example 1: -port1 (use default port address + 1)
+     Example 2: -port2 (use default port address + 2)
+     Example 3: -port0 (don't check for another instance)
+
+  -debug
+     Runs ImageJ in debug mode
+
+@author Wayne Rasband (rasband@gmail.com) +*/ +public class ImageJ extends Frame implements ActionListener, + MouseListener, KeyListener, WindowListener, ItemListener, Runnable { + + /** Plugins should call IJ.getVersion() or IJ.getFullVersion() to get the version string. */ + public static final String VERSION = "1.54s"; + public static final String BUILD = "13"; + public static Color backgroundColor = new Color(237,237,237); + /** SansSerif, 12-point, plain font. */ + public static final Font SansSerif12 = new Font("SansSerif", Font.PLAIN, 12); + /** SansSerif, 14-point, plain font. */ + public static final Font SansSerif14 = new Font("SansSerif", Font.PLAIN, 14); + /** Address of socket where Image accepts commands */ + public static final int DEFAULT_PORT = 57294; + + /** Run as normal application. */ + public static final int STANDALONE = 0; + + /** Run embedded in another application. */ + public static final int EMBEDDED = 1; + + /** Run embedded and invisible in another application. */ + public static final int NO_SHOW = 2; + + /** Run as the ImageJ application. */ + public static final int IMAGEJ_APP = 3; + + /** Run ImageJ in debug mode. */ + public static final int DEBUG = 256; + + private static final String IJ_X="ij.x",IJ_Y="ij.y"; + private static int port = DEFAULT_PORT; + private static String[] arguments; + + private Toolbar toolbar; + private Panel statusBar; + private ProgressBar progressBar; + private JLabel statusLine; + private boolean firstTime = true; + private Applet applet; // null if not running as an applet + private Vector classes = new Vector(); + private boolean exitWhenQuitting; + private boolean quitting; + private boolean quitMacro; + private long keyPressedTime, actionPerformedTime; + private String lastKeyCommand; + private boolean embedded; + private boolean windowClosed; + private static String commandName; + private static boolean batchMode; + + boolean hotkey; + + /** Creates a new ImageJ frame that runs as an application. */ + public ImageJ() { + this(null, STANDALONE); + } + + /** Creates a new ImageJ frame that runs as an application in the specified mode. */ + public ImageJ(int mode) { + this(null, mode); + } + + /** Creates a new ImageJ frame that runs as an applet. + @deprecated Applets were removed in Java 26*/ + @Deprecated(since = "IJ XX; Java 26") + public ImageJ(Applet applet) { + this(applet, STANDALONE); + } + + /** If 'applet' is not null, creates a new ImageJ frame that runs as an applet. + If 'mode' is ImageJ.EMBEDDED and 'applet is null, creates an embedded + (non-standalone) version of ImageJ. + @deprecated Applets were removed in Java 26. + */ + @Deprecated(since = "IJ XX; Java 26") + public ImageJ(Applet applet, int mode) { + super("ImageJ"); + if ((mode&DEBUG)!=0) + IJ.setDebugMode(true); + mode = mode & 255; + boolean useExceptionHandler = false; + if (mode==IMAGEJ_APP) { + mode = STANDALONE; + useExceptionHandler = true; + } + if (IJ.debugMode) IJ.log("ImageJ starting in debug mode: "+mode); + embedded = applet==null && (mode==EMBEDDED||mode==NO_SHOW); + this.applet = applet; + String err1 = Prefs.load(this, applet); + setBackground(backgroundColor); + Menus m = new Menus(this, applet); + String err2 = m.addMenuBar(); + m.installPopupMenu(this); + setLayout(new BorderLayout()); + + // Tool bar + toolbar = new Toolbar(); + toolbar.addKeyListener(this); + add("Center", toolbar); + + // Status bar + statusBar = new Panel(); + statusBar.setLayout(new BorderLayout()); + statusBar.setForeground(Color.black); + statusBar.setBackground(backgroundColor); + statusLine = new JLabel(); + double scale = Prefs.getGuiScale(); + statusLine.setFont(new Font("SansSerif", Font.PLAIN, (int)(13*scale))); + statusLine.addKeyListener(this); + statusLine.addMouseListener(this); + statusBar.add("Center", statusLine); + progressBar = new ProgressBar((int)(ProgressBar.WIDTH*scale), (int)(ProgressBar.HEIGHT*scale)); + progressBar.addKeyListener(this); + progressBar.addMouseListener(this); + statusBar.add("East", progressBar); + add("South", statusBar); + + IJ.init(this, applet); + addKeyListener(this); + addWindowListener(this); + setFocusTraversalKeysEnabled(false); + m.installStartupMacroSet(); //add custom tools + + Point loc = getPreferredLocation(); + Dimension tbSize = toolbar.getPreferredSize(); + setCursor(Cursor.getDefaultCursor()); // work-around for JDK 1.1.8 bug + if (mode!=NO_SHOW) { + if (IJ.isWindows()) try {setIcon();} catch(Exception e) {} + setResizable(false); + setAlwaysOnTop(Prefs.alwaysOnTop); + pack(); + setLocation(loc.x, loc.y); + setVisible(true); + Dimension size = getSize(); + if (size!=null) { + if (IJ.debugMode) IJ.log("size: "+size); + if (IJ.isWindows() && (size.height>108||IJ.javaVersion()>=10)) { + // workaround for IJ window layout and FileDialog freeze problems with Windows 10 Creators Update + IJ.wait(10); + pack(); + if (IJ.debugMode) IJ.log("pack()"); + if (!Prefs.jFileChooserSettingChanged) + Prefs.useJFileChooser = true; + } else if (IJ.isMacOSX()) { + Rectangle maxBounds = GUI.getMaxWindowBounds(this); + if (loc.x+size.width>maxBounds.x+maxBounds.width) + setLocation(loc.x, loc.y); + } + } + } + if (err1!=null) + IJ.error(err1); + if (err2!=null) { + IJ.error(err2); + //IJ.runPlugIn("ij.plugin.ClassChecker", ""); + } + if (IJ.isMacintosh()&&applet==null) { + try { + if (IJ.javaVersion()>8) // newer JREs use different drag-drop, about mechanism + IJ.runPlugIn("ij.plugin.MacAdapter9", ""); + else + IJ.runPlugIn("ij.plugin.MacAdapter", ""); + } catch(Throwable e) {} + } + if (applet==null) + IJ.runPlugIn("ij.plugin.DragAndDrop", ""); + if (!getTitle().contains("Fiji") && useExceptionHandler) { + Thread.setDefaultUncaughtExceptionHandler(new ExceptionHandler()); + System.setProperty("sun.awt.exception.handler",ExceptionHandler.class.getName()); + } + String str = m.getMacroCount()==1?" macro":" macros"; + configureProxy(); + if (applet==null) + loadCursors(); + (new ij.macro.StartupRunner()).run(batchMode); // run RunAtStartup and AutoRun macros + IJ.showStatus(version()+ m.getPluginCount() + " commands; " + m.getMacroCount() + str); + } + + private void loadCursors() { + Toolkit toolkit = Toolkit.getDefaultToolkit(); + String path = Prefs.getImageJDir()+"images/crosshair-cursor.gif"; + File f = new File(path); + if (!f.exists()) + return; + //Image image = toolkit.getImage(path); + ImageIcon icon = new ImageIcon(path); + Image image = icon.getImage(); + if (image==null) + return; + int width = icon.getIconWidth(); + int height = icon.getIconHeight(); + Point hotSpot = new Point(width/2, height/2); + Cursor crosshairCursor = toolkit.createCustomCursor(image, hotSpot, "crosshair-cursor.gif"); + ImageCanvas.setCursor(crosshairCursor, 0); + } + + void configureProxy() { + if (Prefs.useSystemProxies) { + try { + System.setProperty("java.net.useSystemProxies", "true"); + } catch(Exception e) {} + } else { + String server = Prefs.get("proxy.server", null); + if (server==null||server.equals("")) + return; + int port = (int)Prefs.get("proxy.port", 0); + if (port==0) return; + Properties props = System.getProperties(); + props.put("proxySet", "true"); + props.put("http.proxyHost", server); + props.put("http.proxyPort", ""+port); + props.put("https.proxyHost", server); + props.put("https.proxyPort", ""+port); + } + //new ProxySettings().logProperties(); + } + + void setIcon() throws Exception { + URL url = this.getClass().getResource("/microscope.gif"); + if (url==null) return; + Image img = createImage((ImageProducer)url.getContent()); + if (img!=null) setIconImage(img); + } + + public Point getPreferredLocation() { + int ijX = Prefs.getInt(IJ_X,-99); + int ijY = Prefs.getInt(IJ_Y,-99); + Rectangle maxBounds = GUI.getMaxWindowBounds(); + //System.out.println("getPreferredLoc1: "+ijX+" "+ijY+" "+maxBounds); + if (ijX>=maxBounds.x && ijY>=maxBounds.y && ijX<(maxBounds.x+maxBounds.width-75) + && ijY<(maxBounds.y+maxBounds.height-75)) + return new Point(ijX, ijY); + Dimension tbsize = toolbar.getPreferredSize(); + int ijWidth = tbsize.width+10; + double percent = maxBounds.width>832?0.8:0.9; + ijX = (int)(percent*(maxBounds.width-ijWidth)); + if (ijX<10) ijX = 10; + return new Point(ijX, maxBounds.y); + } + + void showStatus(String s) { + statusLine.setText(s); + } + + public ProgressBar getProgressBar() { + return progressBar; + } + + public Panel getStatusBar() { + return statusBar; + } + + public static String getStatusBarText() { + ImageJ ij = IJ.getInstance(); + return ij!=null?ij.statusLine.getText():""; + } + + /** Starts executing a menu command in a separate thread. */ + void doCommand(String name) { + new Executer(name, null); + } + + public void runFilterPlugIn(Object theFilter, String cmd, String arg) { + new PlugInFilterRunner(theFilter, cmd, arg); + } + + public Object runUserPlugIn(String commandName, String className, String arg, boolean createNewLoader) { + return IJ.runUserPlugIn(commandName, className, arg, createNewLoader); + } + + /** Return the current list of modifier keys. */ + public static String modifiers(int flags) { //?? needs to be moved + String s = " [ "; + if (flags == 0) return ""; + if ((flags & Event.SHIFT_MASK) != 0) s += "Shift "; + if ((flags & Event.CTRL_MASK) != 0) s += "Control "; + if ((flags & Event.META_MASK) != 0) s += "Meta "; + if ((flags & Event.ALT_MASK) != 0) s += "Alt "; + s += "] "; + return s; + } + + /** Handle menu events. */ + public void actionPerformed(ActionEvent e) { + if ((e.getSource() instanceof MenuItem)) { + MenuItem item = (MenuItem)e.getSource(); + String cmd = e.getActionCommand(); + Frame frame = WindowManager.getFrontWindow(); + if (frame!=null && (frame instanceof Fitter)) { + ((Fitter)frame).actionPerformed(e); + return; + } + commandName = cmd; + ImagePlus imp = null; + if (item.getParent()==Menus.getOpenRecentMenu()) { + new RecentOpener(cmd); // open image in separate thread + return; + } else if (item.getParent()==Menus.getPopupMenu()) { + Object parent = Menus.getPopupMenu().getParent(); + if (parent instanceof ImageCanvas) + imp = ((ImageCanvas)parent).getImage(); + } + int flags = e.getModifiers(); + hotkey = false; + actionPerformedTime = System.currentTimeMillis(); + long ellapsedTime = actionPerformedTime-keyPressedTime; + if (cmd!=null && (ellapsedTime>=200L||!cmd.equals(lastKeyCommand))) { + if ((flags & Event.ALT_MASK)!=0) + IJ.setKeyDown(KeyEvent.VK_ALT); + if ((flags & Event.SHIFT_MASK)!=0) + IJ.setKeyDown(KeyEvent.VK_SHIFT); + new Executer(cmd, imp); + } + lastKeyCommand = null; + if (IJ.debugMode) IJ.log("actionPerformed: time="+ellapsedTime+", "+e); + } + } + + /** Handles CheckboxMenuItem state changes. */ + public void itemStateChanged(ItemEvent e) { + MenuItem item = (MenuItem)e.getSource(); + MenuComponent parent = (MenuComponent)item.getParent(); + String cmd = e.getItem().toString(); + if ("Autorun Examples".equals(cmd)) // Examples>Autorun Examples + Prefs.autoRunExamples = e.getStateChange()==1; + else if ((Menu)parent==Menus.window) + WindowManager.activateWindow(cmd, item); + else + doCommand(cmd); + } + + public void mousePressed(MouseEvent e) { + Undo.reset(); + if (!Prefs.noClickToGC) + System.gc(); + IJ.showStatus(version()+IJ.freeMemory()); + if (IJ.debugMode) + IJ.log("Windows: "+WindowManager.getWindowCount()); + } + + public String getInfo() { + return version()+System.getProperty("os.name")+" "+System.getProperty("os.version")+"; "+IJ.freeMemory(); + } + + private String version() { + return "ImageJ "+VERSION+BUILD + "; "+"Java "+System.getProperty("java.version")+(IJ.is64Bit()?" [64-bit]; ":" [32-bit]; "); + } + + public void mouseReleased(MouseEvent e) {} + public void mouseExited(MouseEvent e) {} + public void mouseClicked(MouseEvent e) {} + public void mouseEntered(MouseEvent e) {} + + public void keyPressed(KeyEvent e) { + if (e.isConsumed()) + return; + int keyCode = e.getKeyCode(); + IJ.setKeyDown(keyCode); + hotkey = false; + if (keyCode==KeyEvent.VK_CONTROL || keyCode==KeyEvent.VK_SHIFT) + return; + char keyChar = e.getKeyChar(); + int flags = e.getModifiers(); + if (IJ.debugMode) IJ.log("keyPressed: code=" + keyCode + " (" + KeyEvent.getKeyText(keyCode) + + "), char=\"" + keyChar + "\" (" + (int)keyChar + "), flags=" + + KeyEvent.getKeyModifiersText(flags)); + boolean shift = (flags & KeyEvent.SHIFT_MASK) != 0; + boolean control = (flags & KeyEvent.CTRL_MASK) != 0; + boolean alt = (flags & KeyEvent.ALT_MASK) != 0; + boolean meta = (flags & KeyEvent.META_MASK) != 0; + if (keyCode==KeyEvent.VK_H && meta && IJ.isMacOSX()) + return; // Allow macOS to run ImageJ>Hide ImageJ command + String cmd = null; + ImagePlus imp = WindowManager.getCurrentImage(); + boolean isStack = (imp!=null) && (imp.getStackSize()>1); + + if (imp!=null && !meta && ((keyChar>=32 && keyChar<=255) || keyChar=='\b' || keyChar=='\n')) { + Roi roi = imp.getRoi(); + if (roi!=null && roi instanceof TextRoi) { + if (imp.getOverlay()!=null && (control || alt || meta) + && (keyCode==KeyEvent.VK_BACK_SPACE || keyCode==KeyEvent.VK_DELETE)) { + if (deleteOverlayRoi(imp)) + return; + } + if ((flags & KeyEvent.META_MASK)!=0 && IJ.isMacOSX()) + return; + if (alt) { + switch (keyChar) { + case 'u': case 'm': keyChar = IJ.micronSymbol; break; + case 'A': keyChar = IJ.angstromSymbol; break; + default: + } + } + ((TextRoi)roi).addChar(keyChar); + return; + } + } + + // Handle one character macro shortcuts + if (!control && !meta) { + Hashtable macroShortcuts = Menus.getMacroShortcuts(); + if (macroShortcuts.size()>0) { + if (shift) + cmd = (String)macroShortcuts.get(Integer.valueOf(keyCode+200)); + else + cmd = (String)macroShortcuts.get(Integer.valueOf(keyCode)); + if (cmd!=null) { + commandName = cmd; + MacroInstaller.runMacroShortcut(cmd); + return; + } + } + } + + if (keyCode==KeyEvent.VK_SEPARATOR) + keyCode = KeyEvent.VK_DECIMAL; + boolean functionKey = keyCode>=KeyEvent.VK_F1 && keyCode<=KeyEvent.VK_F12; + boolean numPad = keyCode==KeyEvent.VK_DIVIDE || keyCode==KeyEvent.VK_MULTIPLY + || keyCode==KeyEvent.VK_DECIMAL + || (keyCode>=KeyEvent.VK_NUMPAD0 && keyCode<=KeyEvent.VK_NUMPAD9); + if ((!Prefs.requireControlKey||control||meta||functionKey||numPad) && keyChar!='+') { + Hashtable shortcuts = Menus.getShortcuts(); + if (shift && !functionKey) + cmd = (String)shortcuts.get(Integer.valueOf(keyCode+200)); + else + cmd = (String)shortcuts.get(Integer.valueOf(keyCode)); + } + + if (cmd==null) { + switch (keyChar) { + case '<': case ',': if (isStack) cmd="Previous Slice [<]"; break; + case '>': case '.': case ';': if (isStack) cmd="Next Slice [>]"; break; + case '+': case '=': cmd="In [+]"; break; + case '-': cmd="Out [-]"; break; + case '/': cmd="Reslice [/]..."; break; + default: + } + } + + if (cmd==null) { + switch (keyCode) { + case KeyEvent.VK_TAB: WindowManager.putBehind(); return; + case KeyEvent.VK_BACK_SPACE: case KeyEvent.VK_DELETE: + if (!(shift||control||alt||meta)) { + if (deleteOverlayRoi(imp)) + return; + if (imp!=null&&imp.getOverlay()!=null&&imp==GelAnalyzer.getGelImage()) + return; + cmd="Clear"; + hotkey=true; + } + break; + //case KeyEvent.VK_BACK_SLASH: cmd=IJ.altKeyDown()?"Animation Options...":"Start Animation"; break; + case KeyEvent.VK_EQUALS: cmd="In [+]"; break; + case KeyEvent.VK_MINUS: cmd="Out [-]"; break; + case KeyEvent.VK_SLASH: case 0xbf: cmd="Reslice [/]..."; break; + case KeyEvent.VK_COMMA: case 0xbc: if (isStack) cmd="Previous Slice [<]"; break; + case KeyEvent.VK_PERIOD: case 0xbe: if (isStack) cmd="Next Slice [>]"; break; + case KeyEvent.VK_LEFT: case KeyEvent.VK_RIGHT: case KeyEvent.VK_UP: case KeyEvent.VK_DOWN: // arrow keys + if (imp==null) return; + Roi roi = imp.getRoi(); + if (shift&&imp==Orthogonal_Views.getImage()) + return; + if (IJ.isMacOSX() && IJ.isJava18()) { + RoiManager rm = RoiManager.getInstance(); + boolean rmActive = rm!=null && rm==WindowManager.getActiveWindow(); + if (rmActive && (keyCode==KeyEvent.VK_DOWN||keyCode==KeyEvent.VK_UP)) + rm.repaint(); + } + boolean stackKey = imp.getStackSize()>1 && (roi==null||shift); + boolean zoomKey = roi==null || shift || control; + if (stackKey && keyCode==KeyEvent.VK_RIGHT) + cmd="Next Slice [>]"; + else if (stackKey && keyCode==KeyEvent.VK_LEFT) + cmd="Previous Slice [<]"; + else if (zoomKey && keyCode==KeyEvent.VK_DOWN && !ignoreArrowKeys(imp) && Toolbar.getToolId()1 && win!=null && win.getClass().getName().startsWith("loci")) + return true; + return false; + } + + public void keyTyped(KeyEvent e) { + char keyChar = e.getKeyChar(); + int flags = e.getModifiers(); + //if (IJ.debugMode) IJ.log("keyTyped: char=\"" + keyChar + "\" (" + (int)keyChar + // + "), flags= "+Integer.toHexString(flags)+ " ("+KeyEvent.getKeyModifiersText(flags)+")"); + if (keyChar=='\\' || keyChar==171 || keyChar==223) { + if (((flags&Event.ALT_MASK)!=0)) + doCommand("Animation Options..."); + else + doCommand("Start Animation [\\]"); + } + } + + public void keyReleased(KeyEvent e) { + IJ.setKeyUp(e.getKeyCode()); + } + + /** called when escape pressed */ + void abortPluginOrMacro(ImagePlus imp) { + if (imp!=null) { + ImageWindow win = imp.getWindow(); + if (win!=null) { + Roi roi = imp.getRoi(); + if (roi!=null && roi.getState()!=Roi.NORMAL) { + roi.abortModification(imp); + return; + } else { + win.running = false; + win.running2 = false; + } + } + } + Macro.abort(); + Interpreter.abort(); + if (Interpreter.getInstance()!=null) + IJ.beep(); + } + + public void windowClosing(WindowEvent e) { + if (Executer.getListenerCount()>0) + doCommand("Quit"); + else { + quit(); + windowClosed = true; + } + } + + public void windowActivated(WindowEvent e) { + if (IJ.isMacintosh() && !quitting) { + IJ.wait(10); // may be needed for Java 1.4 on OS X + MenuBar mb = Menus.getMenuBar(); + if (mb!=null && mb!=getMenuBar() && !IJ.isMacro()) { + setMenuBar(mb); + Menus.setMenuBarCount++; + //if (IJ.debugMode) IJ.log("setMenuBar: "+Menus.setMenuBarCount); + } + } + } + + public void windowClosed(WindowEvent e) {} + public void windowDeactivated(WindowEvent e) {} + public void windowDeiconified(WindowEvent e) {} + public void windowIconified(WindowEvent e) {} + public void windowOpened(WindowEvent e) {} + + /** Adds the specified class to a Vector to keep it from being + garbage collected, causing static fields to be reset. */ + public void register(Class c) { + if (!classes.contains(c)) + classes.addElement(c); + } + + /** Called by ImageJ when the user selects Quit. */ + public void quit() { + quitMacro = IJ.macroRunning(); + Thread thread = new Thread(this, "Quit"); + thread.setPriority(Thread.NORM_PRIORITY); + thread.start(); + IJ.wait(10); + } + + /** Returns true if ImageJ is exiting. */ + public boolean quitting() { + return quitting; + } + + /** Returns true if ImageJ is quitting as a result of a run("Quit") macro call. */ + public boolean quittingViaMacro() { + return quitting && quitMacro; + } + + /** Called once when ImageJ quits. */ + public void savePreferences(Properties prefs) { + Point loc = getLocation(); + prefs.put(IJ_X, Integer.toString(loc.x)); + prefs.put(IJ_Y, Integer.toString(loc.y)); + } + + public static void main(String args[]) { + boolean noGUI = false; + int mode = IMAGEJ_APP; + arguments = args; + int nArgs = args!=null?args.length:0; + boolean commandLine = false; + for (int i=0; i0 && DEFAULT_PORT+delta<65536) + port = DEFAULT_PORT+delta; + } + } + // If existing ImageJ instance, pass arguments to it and quit. + boolean passArgs = (mode==IMAGEJ_APP||mode==STANDALONE) && !noGUI; + if (IJ.isMacOSX() && !commandLine) + passArgs = false; + if (passArgs && isRunning(args)) + return; + ImageJ ij = IJ.getInstance(); + if (!noGUI && (ij==null || (ij!=null && !ij.isShowing()))) { + ij = new ImageJ(null, mode); + ij.exitWhenQuitting = true; + } else if (batchMode && noGUI) + Prefs.load(null, null); + int macros = 0; + for (int i=0; i0 && arg.indexOf("ij.ImageJ")==-1) { + File file = new File(arg); + IJ.open(file.getAbsolutePath()); + } + } + if (IJ.debugMode && IJ.getInstance()==null && !GraphicsEnvironment.isHeadless()) + new JavaProperties().run(""); + if (noGUI) System.exit(0); + } + + // Is there another instance of ImageJ? If so, send it the arguments and quit. + static boolean isRunning(String args[]) { + return OtherInstance.sendArguments(args); + } + + /** Returns the port that ImageJ checks on startup to see if another instance is running. + * @see ij.OtherInstance + */ + public static int getPort() { + return port; + } + + /** Returns the command line arguments passed to ImageJ. */ + public static String[] getArgs() { + return arguments; + } + + /** ImageJ calls System.exit() when qutting when 'exitWhenQuitting' is true.*/ + public void exitWhenQuitting(boolean ewq) { + exitWhenQuitting = ewq; + } + + /** Quit using a separate thread, hopefully avoiding thread deadlocks. */ + public void run() { + quitting = true; + boolean changes = false; + int[] wList = WindowManager.getIDList(); + if (wList!=null) { + for (int i=0; iMenus.WINDOW_MENU_ITEMS && !(IJ.macroRunning()&&WindowManager.getImageCount()==0)) { + GenericDialog gd = new GenericDialog("ImageJ", this); + gd.addMessage("Are you sure you want to quit ImageJ?"); + gd.showDialog(); + quitting = !gd.wasCanceled(); + windowClosed = false; + } + if (!quitting) + return; + if (!WindowManager.closeAllWindows()) { + quitting = false; + return; + } + if (applet==null) { + saveWindowLocations(); + Prefs.set(ImageWindow.LOC_KEY,null); // don't save image window location + Prefs.savePreferences(); + } + IJ.cleanup(); + dispose(); + if (exitWhenQuitting) + System.exit(0); + } + + void saveWindowLocations() { + Window win = WindowManager.getWindow("B&C"); + if (win!=null) + Prefs.saveLocation(ContrastAdjuster.LOC_KEY, win.getLocation()); + win = WindowManager.getWindow("Threshold"); + if (win!=null) + Prefs.saveLocation(ThresholdAdjuster.LOC_KEY, win.getLocation()); + win = WindowManager.getWindow("Results"); + if (win!=null) { + Prefs.saveLocation(TextWindow.LOC_KEY, win.getLocation()); + Dimension d = win.getSize(); + Prefs.set(TextWindow.WIDTH_KEY, d.width); + Prefs.set(TextWindow.HEIGHT_KEY, d.height); + } + win = WindowManager.getWindow("Log"); + if (win!=null) { + Prefs.saveLocation(TextWindow.LOG_LOC_KEY, win.getLocation()); + Dimension d = win.getSize(); + Prefs.set(TextWindow.LOG_WIDTH_KEY, d.width); + Prefs.set(TextWindow.LOG_HEIGHT_KEY, d.height); + } + win = WindowManager.getWindow("ROI Manager"); + if (win!=null) + Prefs.saveLocation(RoiManager.LOC_KEY, win.getLocation()); + } + + public static String getCommandName() { + return commandName!=null?commandName:"null"; + } + + public static void setCommandName(String name) { + commandName = name; + } + + public void resize() { + double scale = Prefs.getGuiScale(); + toolbar.init(); + statusLine.setFont(new Font("SansSerif", Font.PLAIN, (int)(13*scale))); + progressBar.init((int)(ProgressBar.WIDTH*scale), (int)(ProgressBar.HEIGHT*scale)); + pack(); + } + + /** Handles exceptions on the EDT. */ + public static class ExceptionHandler implements Thread.UncaughtExceptionHandler { + + // for EDT exceptions + public void handle(Throwable thrown) { + handleException(Thread.currentThread().getName(), thrown); + } + + // for other uncaught exceptions + public void uncaughtException(Thread thread, Throwable thrown) { + handleException(thread.getName(), thrown); + } + + protected void handleException(String tname, Throwable e) { + if (Macro.MACRO_CANCELED.equals(e.getMessage())) + return; + CharArrayWriter caw = new CharArrayWriter(); + PrintWriter pw = new PrintWriter(caw); + e.printStackTrace(pw); + String s = caw.toString(); + if (s!=null && s.contains("ij.")) { + if (IJ.getInstance()!=null) + s = IJ.getInstance().getInfo()+"\n"+s; + IJ.log(s); + } + } + + } // inner class ExceptionHandler + +} diff --git a/mrj/26/ij/ImageJApplet.java b/mrj/26/ij/ImageJApplet.java new file mode 100644 index 00000000..e820e01b --- /dev/null +++ b/mrj/26/ij/ImageJApplet.java @@ -0,0 +1,37 @@ +package ij; + +import ij.stub.Applet; + +/** + Runs ImageJ as an applet and optionally opens up to + nine images using URLs passed as a parameters. +

+ Here is an example applet tag that launches ImageJ as an applet + and passes it the URLs of two images: +

+	<applet archive="../ij.jar" code="ij.ImageJApplet.class" width=0 height=0>
+	<param name=url1 value="http://imagej.nih.gov/ij/images/FluorescentCells.jpg">
+	<param name=url2 value="http://imagej.nih.gov/ij/images/blobs.gif">
+	</applet>
+	
+ To use plugins, add them to ij.jar and add entries to IJ_Props.txt file (in ij.jar) that will + create commands for them in the Plugins menu, or a submenu. There are examples + of such entries in IJ.Props.txt, in the "Plugins installed in the Plugins menu" section. +

+ Macros contained in a file named "StartupMacros.txt", in the same directory as the HTML file + containing the applet tag, will be installed on startup. + @deprecated All methods can unconditionally throw since removal of Applets in Java 26. +*/ +@Deprecated(since = "IJ XX; Java 26") +public class ImageJApplet extends Applet { + + /** Starts ImageJ if it's not already running. */ + public void init() { + throw new UnsupportedOperationException("Java 26 has removed Applets"); + } + + public void destroy() { + throw new UnsupportedOperationException("Java 26 has removed Applets"); + } +} + diff --git a/mrj/26/ij/Menus.java b/mrj/26/ij/Menus.java new file mode 100644 index 00000000..d3fca3c2 --- /dev/null +++ b/mrj/26/ij/Menus.java @@ -0,0 +1,1747 @@ +package ij; +import ij.process.*; +import ij.stub.Applet; +import ij.util.*; +import ij.gui.ImageWindow; +import ij.plugin.MacroInstaller; +import ij.gui.Toolbar; +import ij.macro.Interpreter; +import java.awt.*; +import java.awt.image.*; +import java.awt.event.*; +import java.util.*; +import java.io.*; +import java.awt.event.*; +import java.util.zip.*; + +/** +This class installs and updates ImageJ's menus. Note that menu labels, +even in submenus, must be unique. This is because ImageJ uses a single +hash table for all menu labels. If you look closely, you will see that +File->Import->Text Image... and File->Save As->Text Image... do not use +the same label. One of the labels has an extra space. + +@see ImageJ +*/ + +public class Menus { + + public static final char PLUGINS_MENU = 'p'; + public static final char IMPORT_MENU = 'i'; + public static final char SAVE_AS_MENU = 's'; + public static final char SHORTCUTS_MENU = 'h'; // 'h'=hotkey + public static final char ABOUT_MENU = 'a'; + public static final char FILTERS_MENU = 'f'; + public static final char TOOLS_MENU = 't'; + public static final char UTILITIES_MENU = 'u'; + + public static final int WINDOW_MENU_ITEMS = 6; // fixed items at top of Window menu + + public static final int NORMAL_RETURN = 0; + public static final int COMMAND_IN_USE = -1; + public static final int INVALID_SHORTCUT = -2; + public static final int SHORTCUT_IN_USE = -3; + public static final int NOT_INSTALLED = -4; + public static final int COMMAND_NOT_FOUND = -5; + + public static final int MAX_OPEN_RECENT_ITEMS = 15; + + private static Menus instance; + private static MenuBar mbar; + private static CheckboxMenuItem gray8Item,gray16Item,gray32Item, + color256Item,colorRGBItem,RGBStackItem,HSBStackItem,LabStackItem,HSB32Item; + private static PopupMenu popup; + + private static ImageJ ij; + private static Applet applet; + private Hashtable demoImagesTable = new Hashtable(); + private static String ImageJPath, pluginsPath, macrosPath; + private static Properties menus; + private static Properties menuSeparators; + private static Menu pluginsMenu, saveAsMenu, shortcutsMenu, utilitiesMenu, macrosMenu; + static Menu window, openRecentMenu; + private static Hashtable pluginsTable; + + private static int nPlugins, nMacros; + private static Hashtable shortcuts; + private static Hashtable macroShortcuts; + private static Vector pluginsPrefs; // commands saved in IJ_Prefs + static int windowMenuItems2; // non-image windows listed in Window menu + separator + private String error; + private String jarError; + private String pluginError; + private boolean isJarErrorHeading; + private static boolean installingJars, duplicateCommand; + private static Vector jarFiles; // JAR files in plugins folder with "_" in their name + private Map menuEntry2jarFile = new HashMap(); + private static Vector macroFiles; // Macros and scripts in the plugins folder + private static int userPluginsIndex; // First user plugin or submenu in Plugins menu + private static boolean addSorted; + private static int defaultFontSize = IJ.isWindows()?15:0; + private static int fontSize; + private static double scale = 1.0; + private static Font cachedFont; + + static boolean jnlp; // true when using Java WebStart + public static int setMenuBarCount; + + Menus(ImageJ ijInstance, Applet appletInstance) { + ij = ijInstance; + String title = ij!=null?ij.getTitle():null; + applet = appletInstance; + instance = this; + fontSize = Prefs.getInt(Prefs.MENU_SIZE, defaultFontSize); + } + + String addMenuBar() { + scale = Prefs.getGuiScale(); + //if ((scale>=1.5&&scale<2.0) || (scale>=2.5&&scale<3.0)) + // scale = (int)Math.round(scale); + nPlugins = nMacros = userPluginsIndex = 0; + addSorted = installingJars = duplicateCommand = false; + error = null; + mbar = null; + menus = new Properties(); + pluginsTable = new Hashtable(); + shortcuts = new Hashtable(); + pluginsPrefs = new Vector(); + macroShortcuts = null; + setupPluginsAndMacrosPaths(); + Menu file = getMenu("File"); + Menu newMenu = getMenu("File>New", true); + addPlugInItem(file, "Open...", "ij.plugin.Commands(\"open\")", KeyEvent.VK_O, false); + addPlugInItem(file, "Open Next", "ij.plugin.NextImageOpener", KeyEvent.VK_O, true); + Menu openSamples = getMenu("File>Open Samples", true); + openSamples.addSeparator(); + addPlugInItem(openSamples, "Cache Sample Images ", "ij.plugin.URLOpener(\"cache\")", 0, false); + addOpenRecentSubMenu(file); + Menu importMenu = getMenu("File>Import", true); + Menu showFolderMenu = new Menu("Show Folder"); + fixFontSize(showFolderMenu); + file.add(showFolderMenu); + addPlugInItem(showFolderMenu, "Image", "ij.plugin.SimpleCommands(\"showdirImage\")", 0, false); + addPlugInItem(showFolderMenu, "Plugins", "ij.plugin.SimpleCommands(\"showdirPlugins\")", 0, false); + addPlugInItem(showFolderMenu, "Macros", "ij.plugin.SimpleCommands(\"showdirMacros\")", 0, false); + addPlugInItem(showFolderMenu, "LUTs", "ij.plugin.SimpleCommands(\"showdirLuts\")", 0, false); + addPlugInItem(showFolderMenu, "ImageJ", "ij.plugin.SimpleCommands(\"showdirImageJ\")", 0, false); + addPlugInItem(showFolderMenu, "temp", "ij.plugin.SimpleCommands(\"showdirTemp\")", 0, false); + addPlugInItem(showFolderMenu, "Home", "ij.plugin.SimpleCommands(\"showdirHome\")", 0, false); + file.addSeparator(); + addPlugInItem(file, "Close", "ij.plugin.Commands(\"close\")", KeyEvent.VK_W, false); + addPlugInItem(file, "Close All", "ij.plugin.Commands(\"close-all\")", KeyEvent.VK_W, true); + addPlugInItem(file, "Save", "ij.plugin.Commands(\"save\")", KeyEvent.VK_S, false); + saveAsMenu = getMenu("File>Save As", true); + addPlugInItem(file, "Revert", "ij.plugin.Commands(\"revert\")", KeyEvent.VK_R, true); + file.addSeparator(); + addPlugInItem(file, "Page Setup...", "ij.plugin.filter.Printer(\"setup\")", 0, false); + addPlugInItem(file, "Print...", "ij.plugin.filter.Printer(\"print\")", KeyEvent.VK_P, false); + + Menu edit = getMenu("Edit"); + addPlugInItem(edit, "Undo", "ij.plugin.Commands(\"undo\")", KeyEvent.VK_Z, false); + edit.addSeparator(); + addPlugInItem(edit, "Cut", "ij.plugin.Clipboard(\"cut\")", KeyEvent.VK_X, false); + addPlugInItem(edit, "Copy", "ij.plugin.Clipboard(\"copy\")", KeyEvent.VK_C, false); + addPlugInItem(edit, "Copy to System", "ij.plugin.Clipboard(\"scopy\")", 0, false); + addPlugInItem(edit, "Paste", "ij.plugin.Clipboard(\"paste\")", KeyEvent.VK_V, false); + addPlugInItem(edit, "Paste Control...", "ij.plugin.frame.PasteController", 0, false); + edit.addSeparator(); + addPlugInItem(edit, "Clear", "ij.plugin.filter.Filler(\"clear\")", 0, false); + addPlugInItem(edit, "Clear Outside", "ij.plugin.filter.Filler(\"outside\")", 0, false); + addPlugInItem(edit, "Fill", "ij.plugin.filter.Filler(\"fill\")", KeyEvent.VK_F, false); + addPlugInItem(edit, "Draw", "ij.plugin.filter.Filler(\"draw\")", KeyEvent.VK_D, false); + addPlugInItem(edit, "Invert", "ij.plugin.filter.Filters(\"invert\")", KeyEvent.VK_I, true); + edit.addSeparator(); + getMenu("Edit>Selection", true); + Menu optionsMenu = getMenu("Edit>Options", true); + + Menu image = getMenu("Image"); + Menu imageType = getMenu("Image>Type"); + gray8Item = addCheckboxItem(imageType, "8-bit", "ij.plugin.Converter(\"8-bit\")"); + gray16Item = addCheckboxItem(imageType, "16-bit", "ij.plugin.Converter(\"16-bit\")"); + gray32Item = addCheckboxItem(imageType, "32-bit", "ij.plugin.Converter(\"32-bit\")"); + color256Item = addCheckboxItem(imageType, "8-bit Color", "ij.plugin.Converter(\"8-bit Color\")"); + colorRGBItem = addCheckboxItem(imageType, "RGB Color", "ij.plugin.Converter(\"RGB Color\")"); + imageType.add(new MenuItem("-")); + RGBStackItem = addCheckboxItem(imageType, "RGB Stack", "ij.plugin.Converter(\"RGB Stack\")"); + HSBStackItem = addCheckboxItem(imageType, "HSB Stack", "ij.plugin.Converter(\"HSB Stack\")"); + HSB32Item = addCheckboxItem(imageType, "HSB (32-bit)", "ij.plugin.Converter(\"HSB (32-bit)\")"); + LabStackItem = addCheckboxItem(imageType, "Lab Stack", "ij.plugin.Converter(\"Lab Stack\")"); + image.add(imageType); + + image.addSeparator(); + getMenu("Image>Adjust", true); + addPlugInItem(image, "Show Info...", "ij.plugin.ImageInfo", KeyEvent.VK_I, false); + addPlugInItem(image, "Properties...", "ij.plugin.filter.ImageProperties", KeyEvent.VK_P, true); + getMenu("Image>Color", true); + getMenu("Image>Stacks", true); + getMenu("Image>Stacks>Animation_", true); + getMenu("Image>Stacks>Tools_", true); + Menu hyperstacksMenu = getMenu("Image>Hyperstacks", true); + image.addSeparator(); + addPlugInItem(image, "Crop", "ij.plugin.Resizer(\"crop\")", KeyEvent.VK_X, true); + addPlugInItem(image, "Duplicate...", "ij.plugin.Duplicator", KeyEvent.VK_D, true); + addPlugInItem(image, "Rename...", "ij.plugin.SimpleCommands(\"rename\")", 0, false); + addPlugInItem(image, "Scale...", "ij.plugin.Scaler", KeyEvent.VK_E, false); + getMenu("Image>Transform", true); + getMenu("Image>Zoom", true); + getMenu("Image>Overlay", true); + image.addSeparator(); + getMenu("Image>Lookup Tables", true); + + Menu process = getMenu("Process"); + addPlugInItem(process, "Smooth", "ij.plugin.filter.Filters(\"smooth\")", KeyEvent.VK_S, true); + addPlugInItem(process, "Sharpen", "ij.plugin.filter.Filters(\"sharpen\")", 0, false); + addPlugInItem(process, "Find Edges", "ij.plugin.filter.Filters(\"edge\")", 0, false); + addPlugInItem(process, "Find Maxima...", "ij.plugin.filter.MaximumFinder", 0, false); + addPlugInItem(process, "Enhance Contrast...", "ij.plugin.ContrastEnhancer", 0, false); + getMenu("Process>Noise", true); + getMenu("Process>Shadows", true); + getMenu("Process>Binary", true); + getMenu("Process>Math", true); + getMenu("Process>FFT", true); + Menu filtersMenu = getMenu("Process>Filters", true); + process.addSeparator(); + getMenu("Process>Batch", true); + addPlugInItem(process, "Image Calculator...", "ij.plugin.ImageCalculator", 0, false); + addPlugInItem(process, "Subtract Background...", "ij.plugin.filter.BackgroundSubtracter", 0, false); + addItem(process, "Repeat Command", KeyEvent.VK_R, false); + + Menu analyzeMenu = getMenu("Analyze"); + addPlugInItem(analyzeMenu, "Measure", "ij.plugin.filter.Analyzer", KeyEvent.VK_M, false); + addPlugInItem(analyzeMenu, "Analyze Particles...", "ij.plugin.filter.ParticleAnalyzer", 0, false); + addPlugInItem(analyzeMenu, "Summarize", "ij.plugin.filter.Analyzer(\"sum\")", 0, false); + addPlugInItem(analyzeMenu, "Distribution...", "ij.plugin.Distribution", 0, false); + addPlugInItem(analyzeMenu, "Label", "ij.plugin.filter.Filler(\"label\")", 0, false); + addPlugInItem(analyzeMenu, "Clear Results", "ij.plugin.filter.Analyzer(\"clear\")", 0, false); + addPlugInItem(analyzeMenu, "Set Measurements...", "ij.plugin.filter.Analyzer(\"set\")", 0, false); + analyzeMenu.addSeparator(); + addPlugInItem(analyzeMenu, "Set Scale...", "ij.plugin.filter.ScaleDialog", 0, false); + addPlugInItem(analyzeMenu, "Calibrate...", "ij.plugin.filter.Calibrator", 0, false); + if (IJ.isMacOSX()) { + addPlugInItem(analyzeMenu, "Histogram", "ij.plugin.Histogram", 0, false); + shortcuts.put(Integer.valueOf(KeyEvent.VK_H),"Histogram"); + } else + addPlugInItem(analyzeMenu, "Histogram", "ij.plugin.Histogram", KeyEvent.VK_H, false); + addPlugInItem(analyzeMenu, "Plot Profile", "ij.plugin.Profiler(\"plot\")", KeyEvent.VK_K, false); + addPlugInItem(analyzeMenu, "Surface Plot...", "ij.plugin.SurfacePlotter", 0, false); + getMenu("Analyze>Gels", true); + Menu toolsMenu = getMenu("Analyze>Tools", true); + + // the plugins will be added later, after a separator + addPluginsMenu(); + + Menu window = getMenu("Window"); + addPlugInItem(window, "Show All", "ij.plugin.WindowOrganizer(\"show\")", KeyEvent.VK_CLOSE_BRACKET, false); + String key = IJ.isWindows()?"enter":"return"; + addPlugInItem(window, "Main Window ["+key+"]", "ij.plugin.WindowOrganizer(\"imagej\")", 0, false); + addPlugInItem(window, "Put Behind [tab]", "ij.plugin.Commands(\"tab\")", 0, false); + addPlugInItem(window, "Cascade", "ij.plugin.WindowOrganizer(\"cascade\")", 0, false); + addPlugInItem(window, "Tile", "ij.plugin.WindowOrganizer(\"tile\")", 0, false); + window.addSeparator(); + + Menu help = getMenu("Help"); + addPlugInItem(help, "ImageJ Website...", "ij.plugin.BrowserLauncher", 0, false); + help.addSeparator(); + addPlugInItem(help, "Dev. Resources...", "ij.plugin.BrowserLauncher(\""+IJ.URL2+"/developer/index.html\")", 0, false); + addPlugInItem(help, "Macro Functions...", "ij.plugin.BrowserLauncher(\"https://wsr.imagej.net/developer/macro/functions.html\")", 0, false); + Menu examplesMenu = getExamplesMenu(ij); + addPlugInItem(examplesMenu, "Open as Panel", "ij.plugin.SimpleCommands(\"opencp\")", 0, false); + help.add(examplesMenu); + help.addSeparator(); + addPlugInItem(help, "Update ImageJ...", "ij.plugin.ImageJ_Updater", 0, false); + addPlugInItem(help, "Release Notes...", "ij.plugin.BrowserLauncher(\"https://wsr.imagej.net/notes.html\")", 0, false); + addPlugInItem(help, "Refresh Menus", "ij.plugin.ImageJ_Updater(\"menus\")", 0, false); + help.addSeparator(); + Menu aboutMenu = getMenu("Help>About Plugins", true); + addPlugInItem(help, "About ImageJ...", "ij.plugin.AboutBox", 0, false); + + if (applet==null) { + menuSeparators = new Properties(); + installPlugins(); + } + + // make sure "Quit" is the last item in the File menu + file.addSeparator(); + addPlugInItem(file, "Quit", "ij.plugin.Commands(\"quit\")", 0, false); + + //System.out.println("MenuBar.setFont: "+fontSize+" "+scale+" "+getFont()); + if (fontSize!=0 || scale>1.0) + mbar.setFont(getFont()); + if (ij!=null) { + ij.setMenuBar(mbar); + Menus.setMenuBarCount++; + } + + // Add deleted sample images to commands table + pluginsTable.put("Lena (68K)", "ij.plugin.URLOpener(\"lena-std.tif\")"); + pluginsTable.put("Bridge (174K)", "ij.plugin.URLOpener(\"bridge.gif\")"); + + if (pluginError!=null) + error = error!=null?error+="\n"+pluginError:pluginError; + if (jarError!=null) + error = error!=null?error+="\n"+jarError:jarError; + return error; + } + + public static Menu getExamplesMenu(ActionListener listener) { + Menu menu = new Menu("Examples"); + Menu submenu = new Menu("Plots"); + addExample(submenu, "Example Plot", "Example_Plot_.ijm"); + addExample(submenu, "Semi-log Plot", "Semi-log_Plot_.ijm"); + addExample(submenu, "Arrow Plot", "Arrow_Plot_.ijm"); + addExample(submenu, "Damped Wave Plot", "Damped_Wave_Plot_.ijm"); + addExample(submenu, "Dynamic Plot", "Dynamic_Plot_.ijm"); + addExample(submenu, "Dynamic Plot 2D", "Dynamic_Plot_2D_.ijm"); + addExample(submenu, "Custom Plot Symbols", "Custom_Plot_Symbols_.ijm"); + addExample(submenu, "Histograms", "Histograms_.ijm"); + addExample(submenu, "Bar Charts", "Bar_Charts_.ijm"); + addExample(submenu, "Shapes", "Plot_Shapes_.ijm"); + addExample(submenu, "Plot Styles", "Plot_Styles_.ijm"); + addExample(submenu, "Random Data", "Random_Data_.ijm"); + addExample(submenu, "Plot Results", "Plot_Results_.ijm"); + addExample(submenu, "Plot With Spectrum", "Plot_With_Spectrum_.ijm"); + submenu.addActionListener(listener); + menu.add(submenu); + + submenu = new Menu("Tools"); + addExample(submenu, "Annular Selection", "Annular_Selection_Tool.ijm"); + addExample(submenu, "Big Cursor", "Big_Cursor_Tool.ijm"); + addExample(submenu, "Circle Tool", "Circle_Tool.ijm"); + addExample(submenu, "Point Picker", "Point_Picker_Tool.ijm"); + addExample(submenu, "Star Tool", "Star_Tool.ijm"); + addExample(submenu, "Animated Icon Tool", "Animated_Icon_Tool.ijm"); + submenu.addActionListener(listener); + menu.add(submenu); + + submenu = new Menu("Macro"); + addExample(submenu, "Sphere", "Sphere.ijm"); + addExample(submenu, "Dialog Box", "Dialog_Box.ijm"); + addExample(submenu, "Process Folder", "Batch_Process_Folder.ijm"); + addExample(submenu, "OpenDialog Demo", "OpenDialog_Demo.ijm"); + addExample(submenu, "Save All Images", "Save_All_Images.ijm"); + addExample(submenu, "Sine/Cosine Table", "Sine_Cosine_Table.ijm"); + addExample(submenu, "Non-numeric Table", "Non-numeric_Table.ijm"); + addExample(submenu, "Overlay", "Overlay.ijm"); + addExample(submenu, "Stack Overlay", "Stack_Overlay.ijm"); + addExample(submenu, "Array Functions", "Array_Functions.ijm"); + addExample(submenu, "Dual Progress Bars", "Dual_Progress_Bars.ijm"); + addExample(submenu, "Grab Viridis Colormap", "Grab_Viridis_Colormap.ijm"); + addExample(submenu, "Custom Measurement", "Custom_Measurement.ijm"); + addExample(submenu, "Synthetic Images", "Synthetic_Images.ijm"); + addExample(submenu, "Spiral Rotation", "Spiral_Rotation.ijm"); + addExample(submenu, "Curve Fitting", "Curve_Fitting.ijm"); + addExample(submenu, "Colors of 2021", "Colors_of_2021.ijm"); + addExample(submenu, "Turtle Graphics", "Turtle_Graphics.ijm"); + addExample(submenu, "Easter Eggs", "Easter_Eggs.ijm"); + submenu.addActionListener(listener); + menu.add(submenu); + + submenu = new Menu("JavaScript"); + addExample(submenu, "Sphere", "Sphere.js"); + addExample(submenu, "Plasma Cloud", "Plasma_Cloud.js"); + addExample(submenu, "Cloud Debugger", "Cloud_Debugger.js"); + addExample(submenu, "Synthetic Images", "Synthetic_Images.js"); + addExample(submenu, "Points", "Points.js"); + addExample(submenu, "Spiral Rotation", "Spiral_Rotation.js"); + addExample(submenu, "Example Plot", "Example_Plot.js"); + addExample(submenu, "Semi-log Plot", "Semi-log_Plot.js"); + addExample(submenu, "Arrow Plot", "Arrow_Plot.js"); + addExample(submenu, "Dynamic Plot", "Dynamic_Plot.js"); + addExample(submenu, "Plot Styles", "Plot_Styles.js"); + addExample(submenu, "Plot Random Data", "Plot_Random_Data.js"); + addExample(submenu, "Histogram Plots", "Histogram_Plots.js"); + addExample(submenu, "JPEG Quality Plot", "JPEG_Quality_Plot.js"); + addExample(submenu, "Process Folder", "Batch_Process_Folder.js"); + addExample(submenu, "Sine/Cosine Table", "Sine_Cosine_Table.js"); + addExample(submenu, "Non-numeric Table", "Non-numeric_Table.js"); + addExample(submenu, "Overlay", "Overlay.js"); + addExample(submenu, "Stack Overlay", "Stack_Overlay.js"); + addExample(submenu, "Dual Progress Bars", "Dual_Progress_Bars.js"); + addExample(submenu, "Gamma Adjuster", "Gamma_Adjuster.js"); + addExample(submenu, "Custom Measurement", "Custom_Measurement.js"); + addExample(submenu, "Terabyte VirtualStack", "Terabyte_VirtualStack.js"); + addExample(submenu, "Event Listener", "Event_Listener.js"); + addExample(submenu, "FFT Filter", "FFT_Filter.js"); + addExample(submenu, "Curve Fitting", "Curve_Fitting.js"); + addExample(submenu, "Overlay Text", "Overlay_Text.js"); + addExample(submenu, "Crop Multiple Rois", "Crop_Multiple_Rois.js"); + addExample(submenu, "Show all LUTs", "Show_all_LUTs.js"); + addExample(submenu, "Dialog Demo", "Dialog_Demo.js"); + submenu.addActionListener(listener); + menu.add(submenu); + submenu = new Menu("BeanShell"); + addExample(submenu, "Sphere", "Sphere.bsh"); + addExample(submenu, "Example Plot", "Example_Plot.bsh"); + addExample(submenu, "Semi-log Plot", "Semi-log_Plot.bsh"); + addExample(submenu, "Arrow Plot", "Arrow_Plot.bsh"); + addExample(submenu, "Sine/Cosine Table", "Sine_Cosine_Table.bsh"); + submenu.addActionListener(listener); + menu.add(submenu); + submenu = new Menu("Python"); + addExample(submenu, "Sphere", "Sphere.py"); + addExample(submenu, "Animated Gaussian Blur", "Animated_Gaussian_Blur.py"); + addExample(submenu, "Spiral Rotation", "Spiral_Rotation.py"); + addExample(submenu, "Overlay", "Overlay.py"); + submenu.addActionListener(listener); + menu.add(submenu); + submenu = new Menu("Java"); + addExample(submenu, "Sphere", "Sphere_.java"); + addExample(submenu, "Plasma Cloud", "Plasma_Cloud.java"); + addExample(submenu, "Gamma Adjuster", "Gamma_Adjuster.java"); + addExample(submenu, "Plugin", "My_Plugin.java"); + addExample(submenu, "Plugin Filter", "Filter_Plugin.java"); + addExample(submenu, "Plugin Frame", "Plugin_Frame.java"); + addExample(submenu, "Plugin Tool", "Prototype_Tool.java"); + submenu.addActionListener(listener); + menu.add(submenu); + menu.addSeparator(); + CheckboxMenuItem item = new CheckboxMenuItem("Autorun Examples"); + menu.add(item); + item.addItemListener(ij); + item.setState(Prefs.autoRunExamples); + fixFontSize(menu); + return menu; + } + + private static void addExample(Menu menu, String label, String command) { + MenuItem item = new MenuItem(label); + menu.add(item); + item.setActionCommand(command); + fixFontSize(item); + } + + void addOpenRecentSubMenu(Menu menu) { + openRecentMenu = getMenu("File>Open Recent"); + for (int i=0; i0) + key = key.substring(0, index); + for (int count=1; count<100; count++) { + value = Prefs.getString(key + (count/10)%10 + count%10); + if (value==null) + break; + if (count==1) + menu.add(submenu); + if (value.equals("-")) + submenu.addSeparator(); + else + addPluginItem(submenu, value); + } + if (name.equals("Lookup Tables") && applet==null) + addLuts(submenu); + fixFontSize(submenu); + return submenu; + } + + static void addLuts(Menu submenu) { + String path = IJ.getDirectory("luts"); + if (path==null) return; + File f = new File(path); + String[] list = null; + if (applet==null && f.exists() && f.isDirectory()) + list = f.list(); + if (list==null) return; + if (IJ.isLinux() || IJ.isMacOSX()) + Arrays.sort(list); + submenu.addSeparator(); + for (int i=0; i0) { + String shortcut = command.substring(openBracket+1,command.length()-1); + keyCode = convertShortcutToCode(shortcut); + boolean functionKey = keyCode>=KeyEvent.VK_F1 && keyCode<=KeyEvent.VK_F12; + if (keyCode>0 && !functionKey) + command = command.substring(0,openBracket); + } + } + if (keyCode>=KeyEvent.VK_F1 && keyCode<=KeyEvent.VK_F12) { + shortcuts.put(Integer.valueOf(keyCode),command); + keyCode = 0; + } else if (keyCode>=265 && keyCode<=290) { + keyCode -= 200; + shift = true; + } + addItem(submenu,command,keyCode,shift); + while(s.charAt(lastComma+1)==' ' && lastComma+2') { + String submenu = value.substring(2,value.length()-1); + //Menu menu = getMenu("Plugins>" + submenu, true); + Menu menu = addSubMenu(pluginsMenu, submenu); + if (submenu.equals("Shortcuts")) + shortcutsMenu = menu; + else if (submenu.equals("Utilities")) + utilitiesMenu = menu; + else if (submenu.equals("Macros")) + macrosMenu = menu; + } else + addPluginItem(pluginsMenu, value); + } + userPluginsIndex = pluginsMenu.getItemCount(); + if (userPluginsIndex<0) userPluginsIndex = 0; + } + + /** Install plugins using "pluginxx=" keys in IJ_Prefs.txt. + Plugins not listed in IJ_Prefs are added to the end + of the Plugins menu. */ + void installPlugins() { + int nPlugins0 = nPlugins; + String value, className; + char menuCode; + Menu menu; + String[] pluginList = getPlugins(); + String[] pluginsList2 = null; + Hashtable skipList = new Hashtable(); + for (int index=0; index<100; index++) { + value = Prefs.getString("plugin" + (index/10)%10 + index%10); + if (value==null) + break; + menuCode = value.charAt(0); + switch (menuCode) { + case PLUGINS_MENU: default: menu = pluginsMenu; break; + case IMPORT_MENU: menu = getMenu("File>Import"); break; + case SAVE_AS_MENU: menu = getMenu("File>Save As"); break; + case SHORTCUTS_MENU: menu = shortcutsMenu; break; + case ABOUT_MENU: menu = getMenu("Help>About Plugins"); break; + case FILTERS_MENU: menu = getMenu("Process>Filters"); break; + case TOOLS_MENU: menu = getMenu("Analyze>Tools"); break; + case UTILITIES_MENU: menu = utilitiesMenu; break; + } + String prefsValue = value; + value = value.substring(2,value.length()); //remove menu code and coma + className = value.substring(value.lastIndexOf(',')+1,value.length()); + boolean found = className.startsWith("ij."); + if (!found && pluginList!=null) { // does this plugin exist? + if (pluginsList2==null) + pluginsList2 = getStrippedPlugins(pluginList); + for (int i=0; i0) + className = className.substring(0, argStart); + } + skipList.put(className, ""); + } + } + if (pluginList!=null) { + for (int i=0; i0) { + dir = name.substring(0, slashIndex); + name = name.substring(slashIndex+1, name.length()); + menu = getPluginsSubmenu(dir); + slashIndex = name.indexOf('/'); + if (slashIndex>0) { + String dir2 = name.substring(0, slashIndex); + name = name.substring(slashIndex+1, name.length()); + String menuName = "Plugins>"+dir+">"+dir2; + menu = getMenu(menuName); + dir += File.separator+dir2; + } + } + String command = name.replace('_',' '); + if (command.endsWith(".js")||command.endsWith(".py")) + command = command.substring(0, command.length()-3); //remove ".js" or ".py" + else + command = command.substring(0, command.length()-4); //remove ".txt", ".ijm" or ".bsh" + command = command.trim(); + if (pluginsTable.get(command)!=null) // duplicate command? + command = command + " Macro"; + MenuItem item = new MenuItem(command); + addOrdered(menu, item); + item.addActionListener(ij); + String path = (dir!=null?dir+File.separator:"") + name; + pluginsTable.put(command, "ij.plugin.Macro_Runner(\""+path+"\")"); + nMacros++; + } + + static int addPluginSeparatorIfNeeded(Menu menu) { + if (menuSeparators == null) + return 0; + Integer i = (Integer)menuSeparators.get(menu); + if (i == null) { + if (menu.getItemCount() > 0) + addSeparator(menu); + i = Integer.valueOf(menu.getItemCount()); + menuSeparators.put(menu, i); + } + return i.intValue(); + } + + /** Inserts 'item' into 'menu' in alphanumeric order. */ + static void addOrdered(Menu menu, MenuItem item) { + String label = item.getLabel(); + int start = addPluginSeparatorIfNeeded(menu); + for (int i=start; i=3 && !s.startsWith("#")) + entries.add(s); + } + } + catch (IOException e) {} + finally { + try {if (lnr!=null) lnr.close();} + catch (IOException e) {} + } + for (int j=0; j")) { + int firstComma = s.indexOf(','); + if (firstComma==-1 || firstComma<=8) + menu = null; + else { + String name = s.substring(8, firstComma); + menu = getPluginsSubmenu(name); + } + } else if (s.startsWith("\"") || s.startsWith("Plugins")) { + String name = getSubmenuName(jar); + if (name!=null) + menu = getPluginsSubmenu(name); + else + menu = pluginsMenu; + addSorted = true; + } else { + int firstQuote = s.indexOf('"'); + String name = firstQuote<0 ? s : s.substring(0, firstQuote).trim(); + int comma = name.indexOf(','); + if (comma >= 0) + name = name.substring(0, comma); + if (name.startsWith("Help>About")) // for backward compatibility + name = "Help>About Plugins"; + menu = getMenu(name); + } + int firstQuote = s.indexOf('"'); + if (firstQuote==-1) + return; + s = s.substring(firstQuote, s.length()); // remove menu + if (menu!=null) { + addPluginSeparatorIfNeeded(menu); + addPluginItem(menu, s); + addSorted = false; + } + String menuEntry = s; + if (s.startsWith("\"")) { + int quote = s.indexOf('"', 1); + menuEntry = quote<0?s.substring(1):s.substring(1, quote); + } else { + int comma = s.indexOf(','); + if (comma > 0) + menuEntry = s.substring(0, comma); + } + if (duplicateCommand) { + if (jarError==null) jarError = ""; + addJarErrorHeading(jar); + String jar2 = (String)menuEntry2jarFile.get(menuEntry); + if (jar2 != null && jar2.startsWith(pluginsPath)) + jar2 = jar2.substring(pluginsPath.length()); + jarError += " Duplicate command: " + s + + (jar2 != null ? " (already in " + jar2 + ")" + : "") + "\n"; + } else + menuEntry2jarFile.put(menuEntry, jar); + duplicateCommand = false; + } + + void addJarErrorHeading(String jar) { + if (!isJarErrorHeading) { + if (!jarError.equals("")) + jarError += " \n"; + jarError += "Plugin configuration error: " + jar + "\n"; + isJarErrorHeading = true; + } + } + + /** Returns the specified ImageJ menu (e.g., "File>New") or null if it is not found. */ + public static Menu getImageJMenu(String menuPath) { + if (menus==null && !GraphicsEnvironment.isHeadless()) + IJ.init(); + if (menus==null) + return null; + if (menus.get(menuPath)!=null) + return getMenu(menuPath, false); + else + return null; + } + + private static Menu getMenu(String menuPath) { + if (GraphicsEnvironment.isHeadless()) + return null; + else + return getMenu(menuPath, false); + } + + private static Menu getMenu(String menuName, boolean readFromProps) { + if (menuName.endsWith(">")) + menuName = menuName.substring(0, menuName.length() - 1); + Menu result = (Menu)menus.get(menuName); + if (result==null) { + int offset = menuName.lastIndexOf('>'); + if (offset < 0) { + result = new Menu(menuName); + if (mbar == null) + mbar = new MenuBar(); + if (menuName.equals("Help")) + mbar.setHelpMenu(result); + else + mbar.add(result); + if (menuName.equals("Window")) + window = result; + else if (menuName.equals("Plugins")) + pluginsMenu = result; + } else { + String parentName = menuName.substring(0, offset); + String menuItemName = menuName.substring(offset + 1); + Menu parentMenu = getMenu(parentName); + result = new Menu(menuItemName); + addPluginSeparatorIfNeeded(parentMenu); + if (readFromProps) + result = addSubMenu(parentMenu, menuItemName); + else if (parentName.startsWith("Plugins") && menuSeparators != null) + addItemSorted(parentMenu, result, parentName.equals("Plugins")?userPluginsIndex:0); + else + parentMenu.add(result); + if (menuName.equals("File>Open Recent")) + openRecentMenu = result; + } + menus.put(menuName, result); + } + //System.out.println("menuName: "+menuName); + if (IJ.isWindows() && menuName!=null && menuName.contains(">")) + fixFontSize(result); + return result; + } + + Menu getPluginsSubmenu(String submenuName) { + return getMenu("Plugins>" + submenuName); + } + + String getSubmenuName(String jarPath) { + //IJ.log("getSubmenuName: \n"+jarPath+"\n"+pluginsPath); + if (pluginsPath == null) + return null; + if (jarPath.startsWith(pluginsPath)) + jarPath = jarPath.substring(pluginsPath.length() - 1); + int index = jarPath.lastIndexOf(File.separatorChar); + if (index<0) return null; + String name = jarPath.substring(0, index); + index = name.lastIndexOf(File.separatorChar); + if (index<0) return null; + name = name.substring(index+1); + if (name.equals("plugins")) return null; + return name; + } + + static void addItemSorted(Menu menu, MenuItem item, int startingIndex) { + String itemLabel = item.getLabel(); + int count = menu.getItemCount(); + boolean inserted = false; + for (int i=startingIndex; i0 && name.indexOf("$")==-1 + && name.indexOf("/_")==-1 && !name.startsWith("_")) { + if (Character.isLowerCase(name.charAt(0))&&name.indexOf("/")!=-1) + continue; + if (sb==null) sb = new StringBuffer(); + String className = name.substring(0, name.length()-6); + int slashIndex = className.lastIndexOf('/'); + String plugins = "Plugins"; + if (slashIndex >= 0) { + plugins += ">" + className.substring(0, slashIndex).replace('/', '>').replace('_', ' '); + name = className.substring(slashIndex + 1); + } else + name = className; + name = name.replace('_', ' '); + className = className.replace('/', '.'); + sb.append(plugins + ", \""+name+"\", "+className+"\n"); + } + } + jarFile.close(); + } + catch (Throwable e) { + IJ.log(jar+": "+e); + } + if (sb==null) + return null; + else + return new ByteArrayInputStream(sb.toString().getBytes()); + } + + /** Returns a list of the plugins with directory names removed. */ + String[] getStrippedPlugins(String[] plugins) { + String[] plugins2 = new String[plugins.length]; + int slashPos; + for (int i=0; i=0) + plugins2[i] = plugins[i].substring(slashPos+1,plugins2[i].length()); + } + return plugins2; + } + + void setupPluginsAndMacrosPaths() { + ImageJPath = pluginsPath = macrosPath = null; + String currentDir = Prefs.getHomeDir(); // "user.dir" + if (currentDir==null) + return; + if (currentDir.endsWith("plugins")) + ImageJPath = pluginsPath = currentDir+File.separator; + else { + String ijDir = Prefs.getPluginsDirProperty(); + if (ijDir==null) + ijDir = currentDir; + else if (ijDir.equals("user.home")) { + ijDir = System.getProperty("user.home"); + if (!(new File(ijDir+File.separator+"plugins")).isDirectory()) + ijDir = ijDir + File.separator + "ImageJ"; + // needed to run plugins when ImageJ launched using Java WebStart + if (applet==null) + System.setSecurityManager(null); + jnlp = true; + } + pluginsPath = ijDir+File.separator+"plugins"+File.separator; + macrosPath = ijDir+File.separator+"macros"+File.separator; + ImageJPath = ijDir+File.separator; + } + File f = pluginsPath!=null?new File(pluginsPath):null; + if (f==null || !f.isDirectory()) { + ImageJPath = currentDir+File.separator; + pluginsPath = ImageJPath+"plugins"+File.separator; + f = new File(pluginsPath); + if (!f.isDirectory()) { + String altPluginsPath = System.getProperty("plugins.dir"); + if (altPluginsPath!=null) { + f = new File(altPluginsPath); + if (!f.isDirectory()) + altPluginsPath = null; + else { + ImageJPath = f.getParent() + File.separator; + pluginsPath = ImageJPath + f.getName() + File.separator; + macrosPath = ImageJPath+"macros"+File.separator; + } + } + if (altPluginsPath==null) + ImageJPath = pluginsPath = null; + } + } + f = macrosPath!=null?new File(macrosPath):null; + if (f!=null && !f.isDirectory()) { + macrosPath = currentDir+File.separator+"macros"+File.separator; + f = new File(macrosPath); + if (!f.isDirectory()) + macrosPath = null; + } + if (IJ.debugMode) { + IJ.log("Menus.setupPluginsAndMacrosPaths"); + IJ.log(" user.dir: "+currentDir); + IJ.log(" plugins.dir: "+System.getProperty("plugins.dir")); + IJ.log(" ImageJPath: "+ImageJPath); + IJ.log(" pluginsPath: "+pluginsPath); + } + } + + /** Returns a list of the plugins in the plugins menu. */ + public static synchronized String[] getPlugins() { + File f = pluginsPath!=null?new File(pluginsPath):null; + if (f==null || (f!=null && !f.isDirectory())) + return null; + String[] list = f.list(); + if (list==null) + return null; + Vector v = new Vector(); + jarFiles = null; + macroFiles = null; + for (int i=0; i=0; + if (hasUnderscore && isClassFile && name.indexOf('$')<0 ) { + name = name.substring(0, name.length()-6); // remove ".class" + v.addElement(name); + } else if (hasUnderscore && (name.endsWith(".jar") || name.endsWith(".zip"))) { + if (jarFiles==null) jarFiles = new Vector(); + jarFiles.addElement(pluginsPath + name); + } else if (validMacroName(name,hasUnderscore)) { + if (macroFiles==null) macroFiles = new Vector(); + macroFiles.addElement(name); + } else { + if (!isClassFile) + checkSubdirectory(pluginsPath, name, v); + } + } + list = new String[v.size()]; + v.copyInto((String[])list); + StringSorter.sort(list); + return list; + } + + /** Looks for plugins and jar files in a subdirectory of the plugins directory. */ + private static void checkSubdirectory(String path, String dir, Vector v) { + if (dir.endsWith(".java")) + return; + File f = new File(path, dir); + if (!f.isDirectory()) + return; + String[] list = f.list(); + if (list==null) + return; + dir += "/"; + int classCount=0, otherCount=0; + String className = null; + for (int i=0; i=0; + if (hasUnderscore && name.endsWith(".class") && name.indexOf('$')<0) { + name = name.substring(0, name.length()-6); // remove ".class" + v.addElement(dir+name); + classCount++; + className = name; + } else if (hasUnderscore && (name.endsWith(".jar") || name.endsWith(".zip"))) { + if (jarFiles==null) jarFiles = new Vector(); + jarFiles.addElement(f.getPath() + File.separator + name); + otherCount++; + } else if (validMacroName(name,hasUnderscore)) { + if (macroFiles==null) macroFiles = new Vector(); + macroFiles.addElement(dir + name); + otherCount++; + } else { + File f2 = new File(f, name); + if (f2.isDirectory()) installSubdirectorMacros(f2, dir+name); + } + } + if (Prefs.moveToMisc && classCount==1 && otherCount==0 && dir.indexOf("_")==-1) + v.setElementAt("Miscellaneous/" + className, + v.size() - 1); + } + + /** Installs macros and scripts located in subdirectories. */ + private static void installSubdirectorMacros(File f2, String dir) { + if (dir.endsWith("Launchers")) return; + String[] list = f2.list(); + if (list==null) return; + for (int i=0; i=0; + if (validMacroName(name,hasUnderscore)) { + if (macroFiles==null) macroFiles = new Vector(); + macroFiles.addElement(dir+"/"+name); + } + } + } + + private static boolean validMacroName(String name, boolean hasUnderscore) { + return (hasUnderscore&&name.endsWith(".txt")) || name.endsWith(".ijm") + || name.endsWith(".js") || name.endsWith(".bsh") || name.endsWith(".py"); + } + + /** Installs a plugin in the Plugins menu using the class name, + with underscores replaced by spaces, as the command. */ + void installUserPlugin(String className) { + installUserPlugin(className, false); + } + + public void installUserPlugin(String className, boolean force) { + int slashIndex = className.indexOf('/'); + String menuName = slashIndex < 0 ? "Plugins" : "Plugins>" + + className.substring(0, slashIndex).replace('/', '>'); + Menu menu = getMenu(menuName); + String command = className; + if (slashIndex>0) { + command = className.substring(slashIndex+1); + } + command = command.replace('_',' '); + //command = command.trim(); + boolean itemExists = (pluginsTable.get(command)!=null); + if(force && itemExists) + return; + + if (!force && itemExists) // duplicate command? + command = command + " Plugin"; + MenuItem item = new MenuItem(command); + if (force) + addItemSorted(menu,item,0); + else + addOrdered(menu, item); + item.addActionListener(ij); + pluginsTable.put(command, className.replace('/', '.')); + nPlugins++; + } + + void installPopupMenu(ImageJ ij) { + String s; + int count = 0; + MenuItem mi; + popup = new PopupMenu(""); + if (fontSize!=0 || scale>1.0) + popup.setFont(getCachedFont()); + while (true) { + count++; + s = Prefs.getString("popup" + (count/10)%10 + count%10); + if (s==null) + break; + if (s.equals("-")) + popup.addSeparator(); + else if (!s.equals("")) { + mi = new MenuItem(s); + mi.addActionListener(ij); + popup.add(mi); + } + } + } + + public static MenuBar getMenuBar() { + return mbar; + } + + public static Menu getMacrosMenu() { + return macrosMenu; + } + + public static Menu getOpenRecentMenu() { + return openRecentMenu; + } + + public int getMacroCount() { + return nMacros; + } + + public int getPluginCount() { + return nPlugins; + } + + static final int RGB_STACK=10, HSB_STACK=11, LAB_STACK=12, HSB32_STACK=13; + + /** Updates the Image/Type and Window menus. */ + public static void updateMenus() { + if (ij==null) return; + gray8Item.setState(false); + gray16Item.setState(false); + gray32Item.setState(false); + color256Item.setState(false); + colorRGBItem.setState(false); + RGBStackItem.setState(false); + HSBStackItem.setState(false); + LabStackItem.setState(false); + HSB32Item.setState(false); + ImagePlus imp = WindowManager.getCurrentImage(); + if (imp==null) + return; + int type = imp.getType(); + if (imp.getStackSize()>1) { + ImageStack stack = imp.getStack(); + if (stack.isRGB()) + type = RGB_STACK; + else if (stack.isHSB()) + type = HSB_STACK; + else if (stack.isLab()) + type = LAB_STACK; + else if (stack.isHSB32()) + type = HSB32_STACK; + } + switch (type) { + case ImagePlus.GRAY8: + gray8Item.setState(true); + break; + case ImagePlus.GRAY16: + gray16Item.setState(true); + break; + case ImagePlus.GRAY32: + gray32Item.setState(true); + break; + case ImagePlus.COLOR_256: + color256Item.setState(true); + break; + case ImagePlus.COLOR_RGB: + colorRGBItem.setState(true); + break; + case RGB_STACK: + RGBStackItem.setState(true); + break; + case HSB_STACK: + HSBStackItem.setState(true); + break; + case LAB_STACK: + LabStackItem.setState(true); + break; + case HSB32_STACK: + HSB32Item.setState(true); + break; + } + + //update Window menu + int nItems = window.getItemCount(); + int start = WINDOW_MENU_ITEMS + windowMenuItems2; + int index = start + WindowManager.getCurrentIndex(); + try { // workaround for Linux/Java 5.0/bug + for (int i=start; i=2) + index--; + window.insert(item, index); + windowMenuItems2++; + if (windowMenuItems2==1) { + window.insertSeparator(WINDOW_MENU_ITEMS+windowMenuItems2); + windowMenuItems2++; + } + } + + /** Adds one image to the end of the Window menu. */ + static synchronized void addWindowMenuItem(ImagePlus imp) { + if (ij==null) + return; + String name = imp.getTitle(); + String size = ImageWindow.getImageSize(imp); + CheckboxMenuItem item = new CheckboxMenuItem(name+" "+size); + item.setActionCommand("" + imp.getID()); + window.add(item); + fixFontSize(item); + item.addItemListener(ij); + } + + /** Removes the specified item from the Window menu. */ + static synchronized void removeWindowMenuItem(int index) { + //IJ.log("removeWindowMenuItem: "+index+" "+windowMenuItems2+" "+window.getItemCount()); + if (ij==null) + return; + try { + if (index>=0 && index-1) + label = label.substring(0, index); + } + if (item!=null && label.equals(oldLabel) && (imp==null||(""+imp.getID()).equals(cmd))) { + String size = ""; + if (imp!=null) + size = " " + ImageWindow.getImageSize(imp); + item.setLabel(newLabel+size); + return; + } + } + } catch (Exception e) {} + } + + /** Adds a file path to the beginning of the File/Open Recent submenu. */ + public static synchronized void addOpenRecentItem(String path) { + if (ij==null) return; + int count = openRecentMenu.getItemCount(); + for (int i=0; iImport"); break; + case SAVE_AS_MENU: menu = getMenu("File>Save As"); break; + case SHORTCUTS_MENU: menu = shortcutsMenu; break; + case ABOUT_MENU: menu = getMenu("Help>About Plugins"); break; + case FILTERS_MENU: menu = getMenu("Process>Filters"); break; + case TOOLS_MENU: menu = getMenu("Analyze>Tools"); break; + case UTILITIES_MENU: menu = utilitiesMenu; break; + default: return 0; + } + int code = convertShortcutToCode(shortcut); + MenuItem item; + boolean functionKey = code>=KeyEvent.VK_F1 && code<=KeyEvent.VK_F12; + if (code==0) + item = new MenuItem(command); + else if (functionKey) { + command += " [F"+(code-KeyEvent.VK_F1+1)+"]"; + shortcuts.put(Integer.valueOf(code),command); + item = new MenuItem(command); + } else { + shortcuts.put(Integer.valueOf(code),command); + int keyCode = code; + boolean shift = false; + if (keyCode>=265 && keyCode<=290) { + keyCode -= 200; + shift = true; + } + item = new MenuItem(command, new MenuShortcut(keyCode, shift)); + } + menu.add(item); + item.addActionListener(ij); + pluginsTable.put(command, plugin); + shortcut = code>0 && !functionKey?"["+shortcut+"]":""; + pluginsPrefs.addElement(menuCode+",\""+command+shortcut+"\","+plugin); + return NORMAL_RETURN; + } + + /** Deletes a command installed by Plugins/Shortcuts/Add Shortcut. */ + public static int uninstallPlugin(String command) { + boolean found = false; + for (Enumeration en=pluginsPrefs.elements(); en.hasMoreElements();) { + String cmd = (String)en.nextElement(); + if (cmd.contains(command)) { + boolean ok = pluginsPrefs.removeElement((Object)cmd); + found = true; + break; + } + } + if (found) + return NORMAL_RETURN; + else + return COMMAND_NOT_FOUND; + + } + + public static boolean commandInUse(String command) { + if (pluginsTable.get(command)!=null) + return true; + else + return false; + } + + public static int convertShortcutToCode(String shortcut) { + int code = 0; + int len = shortcut.length(); + if (len==2 && shortcut.charAt(0)=='F') { + code = KeyEvent.VK_F1+(int)shortcut.charAt(1)-49; + if (code>=KeyEvent.VK_F1 && code<=KeyEvent.VK_F9) + return code; + else + return 0; + } + if (len==3 && shortcut.charAt(0)=='F') { + code = KeyEvent.VK_F10+(int)shortcut.charAt(2)-48; + if (code>=KeyEvent.VK_F10 && code<=KeyEvent.VK_F12) + return code; + else + return 0; + } + if (len==2 && shortcut.charAt(0)=='N') { // numeric keypad + code = KeyEvent.VK_NUMPAD0+(int)shortcut.charAt(1)-48; + if (code>=KeyEvent.VK_NUMPAD0 && code<=KeyEvent.VK_NUMPAD9) + return code; + switch (shortcut.charAt(1)) { + case '/': return KeyEvent.VK_DIVIDE; + case '*': return KeyEvent.VK_MULTIPLY; + case '-': return KeyEvent.VK_SUBTRACT; + case '+': return KeyEvent.VK_ADD; + case '.': return KeyEvent.VK_DECIMAL; + default: return 0; + } + } + if (len!=1) + return 0; + int c = (int)shortcut.charAt(0); + if (c>=65&&c<=90) //A-Z + code = KeyEvent.VK_A+c-65 + 200; + else if (c>=97&&c<=122) //a-z + code = KeyEvent.VK_A+c-97; + else if (c>=48&&c<=57) //0-9 + code = KeyEvent.VK_0+c-48; + return code; + } + + void installStartupMacroSet() { + if (macrosPath==null) { + MacroInstaller.installFromJar("/macros/StartupMacros.txt"); + return; + } + String path = macrosPath + "StartupMacros.txt"; + File f = new File(path); + if (!f.exists()) { + path = macrosPath + "StartupMacros.ijm"; + f = new File(path); + if (!f.exists()) { + (new MacroInstaller()).installFromIJJar("/macros/StartupMacros.txt"); + return; + } + } else { + if ("StartupMacros.fiji.ijm".equals(f.getName())) + path = f.getPath(); + } + String libraryPath = macrosPath + "Library.txt"; + f = new File(libraryPath); + boolean isLibrary = f.exists(); + try { + MacroInstaller mi = new MacroInstaller(); + if (isLibrary) mi.installLibrary(libraryPath); + mi.installStartupMacros(path); + nMacros += mi.getMacroCount(); + } catch (Exception e) {} + } + + static boolean validShortcut(String shortcut) { + int len = shortcut.length(); + if (shortcut.equals("")) + return true; + else if (len==1) + return true; + else if (shortcut.startsWith("F") && (len==2 || len==3)) + return true; + else + return false; + } + + /** Returns 'true' if this keyboard shortcut is in use. */ + public static boolean shortcutInUse(String shortcut) { + int code = convertShortcutToCode(shortcut); + if (shortcuts.get(Integer.valueOf(code))!=null) + return true; + else + return false; + } + + /** Set the size (in points) used for the fonts in ImageJ menus. + Set the size to 0 to use the Java default size. */ + public static void setFontSize(int size) { + if (size<9 && size!=0) size = 9; + if (size>24) size = 24; + fontSize = size; + } + + /** Returns the size (in points) used for the fonts in ImageJ menus. Returns + 0 if the default font size is being used or if this is a Macintosh. */ + public static int getFontSize() { + return fontSize; + } + + public static Font getFont() { + return getFont(true); + } + + public static Font getFont(boolean checkSize) { + int size = fontSize==0?13:fontSize; + if (size<7) + size = 7; + if (scale>1.0 && !checkSize) + size = 13; + int size0 = size; + size = (int)Math.round(size*scale); + //if (cachedFont==null) System.out.println("getFont: "+size0+" "+size+" "+fontSize+" "+scale+" "+checkSize); + if (checkSize && IJ.isWindows() && size>17) + size = 17; // On Windows, the menu bar font size is set 12 if you set it to >17 + Font menuFont = new Font("SanSerif", Font.PLAIN, size); + return menuFont; + } + + public static Font getCachedFont() { + if (cachedFont==null) + cachedFont = getFont(false); + return cachedFont; + } + + /** Called once when ImageJ quits. */ + public static void savePreferences(Properties prefs) { + if (pluginsPrefs==null) + return; + int index = 0; + for (Enumeration en=pluginsPrefs.elements(); en.hasMoreElements();) { + String key = "plugin" + (index/10)%10 + index%10; + String value = (String)en.nextElement(); + prefs.put(key, value); + index++; + } + int n = openRecentMenu.getItemCount(); + for (int i=0; i"); + if (index==-1 || index==menuPath.length()-1) + return; + String label = menuPath.substring(index+1, menuPath.length()); + menuPath = menuPath.substring(0, index); + pluginsTable.put(label, plugin); + addItem(getMenu(menuPath), label, 0, false); + } + + /** Work around Windows bug that limits menu bar sub-menu to 17 points. */ + private static void fixFontSize(MenuItem item) { + if (IJ.isWindows() && item!=null) + item.setFont(getCachedFont()); + } + +} diff --git a/mrj/26/ij/Prefs.java b/mrj/26/ij/Prefs.java new file mode 100644 index 00000000..ba6ee38b --- /dev/null +++ b/mrj/26/ij/Prefs.java @@ -0,0 +1,833 @@ +package ij; +import ij.stub.Applet; +import ij.util.Java2; +import java.io.*; +import java.util.*; +import java.net.URL; +import java.awt.*; +import ij.io.*; +import ij.util.Tools; +import ij.gui.*; +import ij.plugin.filter.*; +import ij.process.ImageConverter; +import ij.plugin.Animator; +import ij.process.FloatBlitter; +import ij.plugin.GelAnalyzer; +import ij.process.ColorProcessor; +import ij.text.TextWindow; + +/** +This class contains the ImageJ preferences, which are +loaded from the "IJ_Props.txt" and "IJ_Prefs.txt" files. +@see ImageJ +*/ +public class Prefs { + + public static final String PROPS_NAME = "IJ_Props.txt"; + public static final String PREFS_NAME = "IJ_Prefs.txt"; + public static final String DIR_IMAGE = "dir.image"; + public static final String FCOLOR = "fcolor"; + public static final String BCOLOR = "bcolor"; + public static final String ROICOLOR = "roicolor"; + public static final String SHOW_ALL_COLOR = "showcolor"; + public static final String JPEG = "jpeg"; + public static final String FPS = "fps"; + public static final String DIV_BY_ZERO_VALUE = "div-by-zero"; + public static final String NOISE_SD = "noise.sd"; + public static final String MENU_SIZE = "menu.size"; + public static final String GUI_SCALE = "gui.scale"; + public static final String THREADS = "threads"; + public static final String KEY_PREFIX = "."; + + private static final int USE_POINTER=1<<0, ANTIALIASING=1<<1, INTERPOLATE=1<<2, ONE_HUNDRED_PERCENT=1<<3, + BLACK_BACKGROUND=1<<4, JFILE_CHOOSER=1<<5, UNUSED=1<<6, BLACK_CANVAS=1<<7, WEIGHTED=1<<8, + AUTO_MEASURE=1<<9, REQUIRE_CONTROL=1<<10, USE_INVERTING_LUT=1<<11, ANTIALIASED_TOOLS=1<<12, + INTEL_BYTE_ORDER=1<<13, DOUBLE_BUFFER=1<<14, NO_POINT_LABELS=1<<15, NO_BORDER=1<<16, + SHOW_ALL_SLICE_ONLY=1<<17, COPY_HEADERS=1<<18, NO_ROW_NUMBERS=1<<19, + MOVE_TO_MISC=1<<20, ADD_TO_MANAGER=1<<21, RUN_SOCKET_LISTENER=1<<22, + MULTI_POINT_MODE=1<<23, ROTATE_YZ=1<<24, FLIP_XZ=1<<25, + DONT_SAVE_HEADERS=1<<26, DONT_SAVE_ROW_NUMBERS=1<<27, NO_CLICK_TO_GC=1<<28, + AVOID_RESLICE_INTERPOLATION=1<<29, KEEP_UNDO_BUFFERS=1<<30; + public static final String OPTIONS = "prefs.options"; + + public static final String vistaHint = ""; // no longer used + + private static final int USE_SYSTEM_PROXIES=1<<0, USE_FILE_CHOOSER=1<<1, + SUBPIXEL_RESOLUTION=1<<2, ENHANCED_LINE_TOOL=1<<3, SKIP_RAW_DIALOG=1<<4, + REVERSE_NEXT_PREVIOUS_ORDER=1<<5, AUTO_RUN_EXAMPLES=1<<6, SHOW_ALL_POINTS=1<<7, + DO_NOT_SAVE_WINDOW_LOCS=1<<8, JFILE_CHOOSER_CHANGED=1<<9, + CANCEL_BUTTON_ON_RIGHT=1<<10, IGNORE_RESCALE_SLOPE=1<<11, + NON_BLOCKING_DIALOGS=1<<12, FIXED_DICOM_SCALING=1<<13, + CALIBRATE_CONVERSIONS=1<<14; + public static final String OPTIONS2 = "prefs.options2"; + + /** file.separator system property */ + public static String separator = System.getProperty("file.separator"); + /** Use pointer cursor instead of cross */ + public static boolean usePointerCursor; + /** No longer used */ + public static boolean antialiasedText; + /** Display images scaled <100% using bilinear interpolation */ + public static boolean interpolateScaledImages; + /** Open images at 100% magnification*/ + public static boolean open100Percent; + /** Backgound is black in binary images*/ + public static boolean blackBackground; + /** Use JFileChooser instead of FileDialog to open and save files. */ + public static boolean useJFileChooser; + /** Color to grayscale conversion is weighted (0.299, 0.587, 0.114) if the variable is true. */ + public static boolean weightedColor; + /** Use black image border. */ + public static boolean blackCanvas; + /** Point tool auto-measure mode. */ + public static boolean pointAutoMeasure; + /** Point tool auto-next slice mode (not saved in IJ_Prefs). */ + public static boolean pointAutoNextSlice; + /** Require control or command key for keybaord shortcuts. */ + public static boolean requireControlKey; + /** Open 8-bit images with inverting LUT so 0 is white and 255 is black. */ + public static boolean useInvertingLut; + /** Draw tool icons using antialiasing (always true). */ + public static boolean antialiasedTools = true; + /** Export TIFF and Raw using little-endian byte order. */ + public static boolean intelByteOrder = true; + /** No longer used */ + public static boolean doubleBuffer = true; + /** Do not label multiple points created using point tool. */ + public static boolean noPointLabels; + /** Disable Edit/Undo command. */ + public static boolean disableUndo; + /** Do not draw black border around image. */ + public static boolean noBorder; + /** Only show ROIs associated with current slice in Roi Manager "Show All" mode. */ + public static boolean showAllSliceOnly = true; + /** Include column headers when copying tables to clipboard. */ + public static boolean copyColumnHeaders; + /** Do not include row numbers when copying tables to clipboard. */ + public static boolean noRowNumbers; + /** Move isolated plugins to Miscellaneous submenu. */ + public static boolean moveToMisc; + /** Add points to ROI Manager. */ + public static boolean pointAddToManager; + /** Add points to overlay. */ + public static boolean pointAddToOverlay; + /** Extend the borders to foreground for binary erosions and closings. */ + public static boolean padEdges; + /** Run the SocketListener. */ + public static boolean runSocketListener; + /** Use MultiPoint tool. */ + public static boolean multiPointMode; + /** Open DICOMs as 32-bit float images */ + public static boolean openDicomsAsFloat; + /** Ignore Rescale Slope when opening DICOMs */ + public static boolean ignoreRescaleSlope; + /** Assume DICOM volumes use identical RescaleSlope and RescaleIntercept across all slices */ + public static boolean fixedDicomScaling; + /** Plot rectangular selectons vertically */ + public static boolean verticalProfile; + /** Rotate YZ orthogonal views 90 degrees */ + public static boolean rotateYZ; + /** Rotate XZ orthogonal views 180 degrees */ + public static boolean flipXZ; + /** Don't save Results table column headers */ + public static boolean dontSaveHeaders; + /** Don't save Results table row numbers */ + public static boolean dontSaveRowNumbers; + /** Don't run garbage collector when user clicks in status bar */ + public static boolean noClickToGC; + /** Angle tool measures reflex angle */ + public static boolean reflexAngle; + /** Avoid interpolation when re-slicing */ + public static boolean avoidResliceInterpolation; + /** Preserve undo (snapshot) buffers when switching images */ + public static boolean keepUndoBuffers; + /** Use ROI names as "show all" labels in the ROI Manager */ + public static boolean useNamesAsLabels; + /** Set the "java.net.useSystemProxies" property */ + public static boolean useSystemProxies; + /** Use the file chooser to import and export image sequences on Windows and Linux*/ + public static boolean useFileChooser; + /** Use sub-pixel resolution with line selections */ + public static boolean subPixelResolution; + /** Adjust contrast when scrolling stacks */ + public static boolean autoContrast; + /** Allow lines to be created with one click at start and another at the end */ + public static boolean enhancedLineTool; + /** Keep arrow selection after adding to overlay */ + public static boolean keepArrowSelections; + /** Aways paint images using double buffering */ + public static boolean paintDoubleBuffered; + /** Do not display dialog when opening .raw files */ + public static boolean skipRawDialog; + /** Reverse channel-slice-frame priority used by Next Slice and Previous Slice commands. */ + public static boolean reverseNextPreviousOrder; + /** Automatically run examples in Help/Examples menu. */ + public static boolean autoRunExamples = true; + /** Ignore stack positions when displaying points. */ + public static boolean showAllPoints; + /** Show ImageJ menu bar on image window activation on Macs. */ + public static boolean setIJMenuBar = IJ.isMacOSX(); + /** "ImageJ" window is always on top. */ + public static boolean alwaysOnTop; + /** Automatically spline fit line selections */ + public static boolean splineFitLines; + /** Enable this option to workaround a bug with some Linux window + managers that causes windows to wander down the screen. */ + public static boolean doNotSaveWindowLocations; + /** Use JFileChooser setting changed/ */ + public static boolean jFileChooserSettingChanged; + /** Convert tiff units to microns if pixel width is less than 0.0001 cm. */ + public static boolean convertToMicrons = true; + /** Wand tool "Smooth if thresholded" option */ + public static boolean smoothWand; + /** "Close All" command running */ + public static boolean closingAll; + /** Dialog "Cancel" button is on right on Linux */ + public static boolean dialogCancelButtonOnRight; + /** Support TRANSFORM Undo in macros */ + public static boolean supportMacroUndo; + /** Use NonBlockingGenericDialogs in filters */ + public static boolean nonBlockingFilterDialogs; + /** Turn live display on plots automatically */ + public static boolean autoLivePlots; + /** Use full range for 16-bit inversions */ + public static boolean fullRange16bitInversions; + /** Calibrate image type conversions */ + public static boolean calibrateConversions; + /** Open grayscale RGB JPEGs as RGB */ + public static boolean openGrayscaleJpegsAsRGB; + /** Scroll stacks using mouse wheel */ + public static boolean mouseWheelStackScrolling = true; + + //Save location of moved image windows */ + //public static boolean saveImageLocation = true; + + static boolean commandLineMacro; + static Properties ijPrefs = new Properties(); + static Properties props = new Properties(ijPrefs); + static String prefsDir; + static String imagesURL; + static String ImageJDir; + static String pluginsDirProperty; + static int threads; + static int transparentIndex = -1; + private static boolean resetPreferences; + private static double guiScale = 1.0; + private static Properties locKeys = new Properties(); + private static String propertiesPath; // location of custom IJ_Props.txt + private static String preferencesPath; // location of custom IJ_Prefs.txt + + /** Saves the value of the string text in the preferences + * file using the keyword key. The string can be + * retrieved using the appropriate get() method. + * @see #get(String,String) + */ + public static void set(String key, String text) { + if (key.indexOf('.')<1) + throw new IllegalArgumentException("Key must have a prefix"); + if (text==null) + ijPrefs.remove(KEY_PREFIX+key); + else + ijPrefs.put(KEY_PREFIX+key, text); + } + + /** Saves the value of the integer value in the preferences + * file using the keyword key. The value can be + * retrieved using the appropriate get() method. + * @see #get(String,double) + */ + public static void set(String key, int value) { + set(key, Integer.toString(value)); + } + + /** Saves the value of the double value in the preferences + * file using the keyword key. The value can be + * retrieved using the appropriate get() method. + * @see #get(String,double) + */ + public static void set(String key, double value) { + set(key, ""+value); + } + + /** Saves the value of the boolean value in the preferences + * file using the keyword key. The value can be + * retrieved using the appropriate get() method. + * @see #get(String,boolean) + */ + public static void set(String key, boolean value) { + set(key, ""+value); + } + + /** Uses the keyword key to retrieve a string from the + preferences file. Returns defaultValue if the key + is not found. */ + public static String get(String key, String defaultValue) { + String value = ijPrefs.getProperty(KEY_PREFIX+key); + if (value == null) + return defaultValue; + else + return value; + } + + /** Uses the keyword key to retrieve a number from the + preferences file. Returns defaultValue if the key + is not found. */ + public static double get(String key, double defaultValue) { + String s = ijPrefs.getProperty(KEY_PREFIX+key); + Double d = null; + if (s!=null) { + try {d = Double.valueOf(s);} + catch (NumberFormatException e) {d = null;} + if (d!=null) + return(d.doubleValue()); + } + return defaultValue; + } + + /** Uses the keyword key to retrieve a boolean from + the preferences file. Returns defaultValue if + the key is not found. */ + public static boolean get(String key, boolean defaultValue) { + String value = ijPrefs.getProperty(KEY_PREFIX+key); + if (value==null) + return defaultValue; + else + return value.equals("true"); + } + + /** + * Finds and loads the configuration file ("IJ_Props.txt") + * and the preferences file ("IJ_Prefs.txt"). + * + * @param ij + * @return an error message if "IJ_Props.txt" not found. + */ + public static String load(Object ij) { + return load(ij, null); + } + + /** Finds and loads the configuration file ("IJ_Props.txt") + * and the preferences file ("IJ_Prefs.txt"). + * @return an error message if "IJ_Props.txt" not found. + */ + @Deprecated(since = "IJ XX; Java 26") + public static String load(Object ij, Applet applet) { + if (ImageJDir==null) + ImageJDir = System.getProperty("user.dir"); + if (ij!=null) { + InputStream f = null; + try { // Look for IJ_Props.txt in ImageJ folder + f = new FileInputStream(ImageJDir+"/"+PROPS_NAME); + propertiesPath = ImageJDir+"/"+PROPS_NAME; + } catch (FileNotFoundException e) { + f = null; + } + if (f==null) { + // Look in ij.jar if not found in ImageJ folder + f = ij.getClass().getResourceAsStream("/"+PROPS_NAME); + } + if (applet!=null) + return loadAppletProps(f, applet); + if (f==null) + return PROPS_NAME+" not found in ij.jar or in "+ImageJDir; + f = new BufferedInputStream(f); + try { + props.load(f); + f.close(); + } catch (IOException e) { + return("Error loading "+PROPS_NAME); + } + imagesURL = props.getProperty(IJ.isJava18()?"images.location":"images.location2"); + } + loadPreferences(); + loadOptions(); + guiScale = get(GUI_SCALE, 1.0); + return null; + } + + /* + static void dumpPrefs() { + System.out.println(""); + Enumeration e = ijPrefs.keys(); + while (e.hasMoreElements()) { + String key = (String) e.nextElement(); + System.out.println(key+": "+ijPrefs.getProperty(key)); + } + } + */ + + @Deprecated(since = "IJ XX; Java 26") + static String loadAppletProps(InputStream f, Applet applet) { + if (f==null) + return PROPS_NAME+" not found in ij.jar"; + try { + props.load(f); + f.close(); + } + catch (IOException e) {return("Error loading "+PROPS_NAME);} + try { + URL url = new URL(applet.getDocumentBase(), "images/"); + imagesURL = url.toString(); + } + catch (Exception e) {} + return null; + } + + /** Returns the URL of the directory that contains the ImageJ sample images. */ + public static String getImagesURL() { + return imagesURL; + } + + /** Sets the URL of the directory that contains the ImageJ sample images. */ + public static void setImagesURL(String url) { + imagesURL = url; + } + + /** Obsolete, replaced by getImageJDir(), which, unlike this method, + returns a path that ends with File.separator. */ + public static String getHomeDir() { + return ImageJDir; + } + + /** Returns the path, ending in File.separator, to the ImageJ directory. */ + public static String getImageJDir() { + String path = Menus.getImageJPath(); + if (path==null) { + String ijPath = ImageJDir; + if (ijPath==null) + ijPath = getPluginsDirProperty(); + if (ijPath==null) + ijPath = System.getProperty("user.dir"); + return ijPath + File.separator; + } else + return path; + } + + public static String getPluginsDirProperty() { + if (pluginsDirProperty==null) { + String ijDir = System.getProperty("plugins.dir"); + if (ijDir!=null) { + if (ijDir.endsWith("/")||ijDir.endsWith("\\")) + ijDir = ijDir.substring(0, ijDir.length()-1); + if (ijDir.endsWith("/plugins")||ijDir.endsWith("\\plugins")) + ijDir = ijDir.substring(0, ijDir.length()-8); + pluginsDirProperty = ijDir; + } else + pluginsDirProperty = ""; + } + return pluginsDirProperty.length()>0?pluginsDirProperty:null; + } + + /** Returns the path to the directory where the + preferences file (IJPrefs.txt) is saved. */ + public static String getPrefsDir() { + // look in current directory + if (prefsDir==null) { + String cwd = System.getProperty("user.dir"); + File f = new File(cwd+File.separator+PREFS_NAME); + if (f.exists()) { + prefsDir = cwd; + preferencesPath = cwd+"/"+PREFS_NAME; + } + // look in ImageJ directory + if (prefsDir==null) { + String ijDir = getImageJDir(); + ijDir = ijDir.substring(0, ijDir.length()-1); + f = new File(ijDir+File.separator+PREFS_NAME); + if (f.exists()) { + prefsDir = ijDir; + preferencesPath = ijDir+"/"+PREFS_NAME; + } + } + // use home directory + if (prefsDir==null) { + String dir = System.getProperty("user.home"); + if (IJ.isMacOSX()) + dir += "/Library/Preferences"; + else + dir += File.separator+".imagej"; + prefsDir = dir; + } + } + return prefsDir; + } + + /** Sets the path to the ImageJ directory. */ + static void setHomeDir(String path) { + if (path.endsWith(File.separator) || path.endsWith("/")) + path = path.substring(0, path.length()-1); + ImageJDir = path; + } + + /** Returns the default directory, if any, or null. */ + public static String getDefaultDirectory() { + if (commandLineMacro) + return null; + else + return getString(DIR_IMAGE); + } + + /** Returns the file.separator system property. */ + public static String getFileSeparator() { + return separator; + } + + /** Opens the ImageJ preferences file ("IJ_Prefs.txt") file. */ + static void loadPreferences() { + String path = getPrefsDir()+separator+PREFS_NAME; + boolean ok = loadPrefs(path); + if (!ok) { // not found + if (IJ.isWindows()) + path = ImageJDir +separator+PREFS_NAME; + else + path = System.getProperty("user.home")+separator+PREFS_NAME; //User's home dir + ok = loadPrefs(path); + if (ok) + new File(path).delete(); + } + + } + + static boolean loadPrefs(String path) { + try { + InputStream is = new BufferedInputStream(new FileInputStream(path)); + ijPrefs.load(is); + is.close(); + return true; + } catch (Exception e) { + return false; + } + } + + /** Saves user preferences in the IJ_Prefs.txt properties file. */ + public static void savePreferences() { + String path = null; + commandLineMacro = false; + try { + Properties prefs = new Properties(); + String dir = OpenDialog.getDefaultDirectory(); + if (dir!=null) + prefs.put(DIR_IMAGE, dir); + prefs.put(ROICOLOR, Tools.c2hex(Roi.getColor())); + prefs.put(SHOW_ALL_COLOR, Tools.c2hex(ImageCanvas.getShowAllColor())); + prefs.put(FCOLOR, Tools.c2hex(Toolbar.getForegroundColor())); + prefs.put(BCOLOR, Tools.c2hex(Toolbar.getBackgroundColor())); + prefs.put(JPEG, Integer.toString(FileSaver.getJpegQuality())); + prefs.put(FPS, Double.toString(Animator.getFrameRate())); + prefs.put(DIV_BY_ZERO_VALUE, Double.toString(FloatBlitter.divideByZeroValue)); + prefs.put(NOISE_SD, Double.toString(Filters.getSD())); + if (threads>1) prefs.put(THREADS, Integer.toString(threads)); + if (IJ.isMacOSX()) useJFileChooser = false; + if (!IJ.isLinux()) dialogCancelButtonOnRight = false; + saveOptions(prefs); + savePluginPrefs(prefs); + ImageJ ij = IJ.getInstance(); + if (ij!=null) + ij.savePreferences(prefs); + Menus.savePreferences(prefs); + ParticleAnalyzer.savePreferences(prefs); + Analyzer.savePreferences(prefs); + ImportDialog.savePreferences(prefs); + PlotWindow.savePreferences(prefs); + NewImage.savePreferences(prefs); + String prefsDir = getPrefsDir(); + path = prefsDir+separator+PREFS_NAME; + if (prefsDir.endsWith(".imagej")) { + File f = new File(prefsDir); + if (!f.exists()) f.mkdir(); // create .imagej directory + } + if (resetPreferences) { + File f = new File(path); + if (!f.exists()) + IJ.error("Edit>Options>Reset", "Unable to reset preferences. File not found at\n"+path); + boolean rtn = f.delete(); + resetPreferences = false; + } else + savePrefs(prefs, path); + } catch (Throwable t) { + String msg = t.getMessage(); + if (msg==null) msg = ""+t; + int delay = 4000; + try { + new TextWindow("Error Saving Preferences:\n"+path, msg, 500, 200); + IJ.wait(delay); + } catch (Throwable t2) {} + } + } + + /** Delete the preferences file when ImageJ quits. */ + public static void resetPreferences() { + resetPreferences = true; + } + + static void loadOptions() { + int defaultOptions = ANTIALIASING+AVOID_RESLICE_INTERPOLATION+ANTIALIASED_TOOLS+MULTI_POINT_MODE + +(!IJ.isMacOSX()?RUN_SOCKET_LISTENER:0)+BLACK_BACKGROUND; + int options = getInt(OPTIONS, defaultOptions); + usePointerCursor = (options&USE_POINTER)!=0; + //antialiasedText = (options&ANTIALIASING)!=0; + antialiasedText = false; + interpolateScaledImages = (options&INTERPOLATE)!=0; + open100Percent = (options&ONE_HUNDRED_PERCENT)!=0; + blackBackground = (options&BLACK_BACKGROUND)!=0; + useJFileChooser = (options&JFILE_CHOOSER)!=0; + weightedColor = (options&WEIGHTED)!=0; + if (weightedColor) + ColorProcessor.setWeightingFactors(0.299, 0.587, 0.114); + blackCanvas = (options&BLACK_CANVAS)!=0; + requireControlKey = (options&REQUIRE_CONTROL)!=0; + useInvertingLut = (options&USE_INVERTING_LUT)!=0; + intelByteOrder = (options&INTEL_BYTE_ORDER)!=0; + noBorder = (options&NO_BORDER)!=0; + //showAllSliceOnly = (options&SHOW_ALL_SLICE_ONLY)!=0; + copyColumnHeaders = (options©_HEADERS)!=0; + noRowNumbers = (options&NO_ROW_NUMBERS)!=0; + moveToMisc = (options&MOVE_TO_MISC)!=0; + runSocketListener = (options&RUN_SOCKET_LISTENER)!=0; + multiPointMode = (options&MULTI_POINT_MODE)!=0; + rotateYZ = (options&ROTATE_YZ)!=0; + flipXZ = (options&FLIP_XZ)!=0; + //dontSaveHeaders = (options&DONT_SAVE_HEADERS)!=0; + //dontSaveRowNumbers = (options&DONT_SAVE_ROW_NUMBERS)!=0; + noClickToGC = (options&NO_CLICK_TO_GC)!=0; + avoidResliceInterpolation = (options&AVOID_RESLICE_INTERPOLATION)!=0; + keepUndoBuffers = (options&KEEP_UNDO_BUFFERS)!=0; + + defaultOptions = (!IJ.isMacOSX()?USE_FILE_CHOOSER:0); + int options2 = getInt(OPTIONS2, defaultOptions); + useSystemProxies = (options2&USE_SYSTEM_PROXIES)!=0; + useFileChooser = (options2&USE_FILE_CHOOSER)!=0; + subPixelResolution = (options2&SUBPIXEL_RESOLUTION)!=0; + enhancedLineTool = (options2&ENHANCED_LINE_TOOL)!=0; + skipRawDialog = (options2&SKIP_RAW_DIALOG)!=0; + reverseNextPreviousOrder = (options2&REVERSE_NEXT_PREVIOUS_ORDER)!=0; + autoRunExamples = (options2&AUTO_RUN_EXAMPLES)!=0; + showAllPoints = (options2&SHOW_ALL_POINTS)!=0; + doNotSaveWindowLocations = (options2&DO_NOT_SAVE_WINDOW_LOCS)!=0; + jFileChooserSettingChanged = (options2&JFILE_CHOOSER_CHANGED)!=0; + dialogCancelButtonOnRight = (options2&CANCEL_BUTTON_ON_RIGHT)!=0; + ignoreRescaleSlope = (options2&IGNORE_RESCALE_SLOPE)!=0; + nonBlockingFilterDialogs = (options2&NON_BLOCKING_DIALOGS)!=0; + fixedDicomScaling = (options2&FIXED_DICOM_SCALING)!=0; + //calibrateConversions = (options2&CALIBRATE_CONVERSIONS)!=0; + } + + static void saveOptions(Properties prefs) { + int options = (usePointerCursor?USE_POINTER:0) + (antialiasedText?ANTIALIASING:0) + + (interpolateScaledImages?INTERPOLATE:0) + (open100Percent?ONE_HUNDRED_PERCENT:0) + + (blackBackground?BLACK_BACKGROUND:0) + (useJFileChooser?JFILE_CHOOSER:0) + + (blackCanvas?BLACK_CANVAS:0) + (weightedColor?WEIGHTED:0) + + (requireControlKey?REQUIRE_CONTROL:0) + + (useInvertingLut?USE_INVERTING_LUT:0) + + (intelByteOrder?INTEL_BYTE_ORDER:0) + (doubleBuffer?DOUBLE_BUFFER:0) + + (noPointLabels?NO_POINT_LABELS:0) + (noBorder?NO_BORDER:0) + + (showAllSliceOnly?SHOW_ALL_SLICE_ONLY:0) + (copyColumnHeaders?COPY_HEADERS:0) + + (noRowNumbers?NO_ROW_NUMBERS:0) + (moveToMisc?MOVE_TO_MISC:0) + + (runSocketListener?RUN_SOCKET_LISTENER:0) + + (multiPointMode?MULTI_POINT_MODE:0) + (rotateYZ?ROTATE_YZ:0) + + (flipXZ?FLIP_XZ:0) + (dontSaveHeaders?DONT_SAVE_HEADERS:0) + + (dontSaveRowNumbers?DONT_SAVE_ROW_NUMBERS:0) + (noClickToGC?NO_CLICK_TO_GC:0) + + (avoidResliceInterpolation?AVOID_RESLICE_INTERPOLATION:0) + + (keepUndoBuffers?KEEP_UNDO_BUFFERS:0); + prefs.put(OPTIONS, Integer.toString(options)); + + int options2 = (useSystemProxies?USE_SYSTEM_PROXIES:0) + + (useFileChooser?USE_FILE_CHOOSER:0) + (subPixelResolution?SUBPIXEL_RESOLUTION:0) + + (enhancedLineTool?ENHANCED_LINE_TOOL:0) + (skipRawDialog?SKIP_RAW_DIALOG:0) + + (reverseNextPreviousOrder?REVERSE_NEXT_PREVIOUS_ORDER:0) + + (autoRunExamples?AUTO_RUN_EXAMPLES:0) + (showAllPoints?SHOW_ALL_POINTS:0) + + (doNotSaveWindowLocations?DO_NOT_SAVE_WINDOW_LOCS:0) + + (jFileChooserSettingChanged?JFILE_CHOOSER_CHANGED:0) + + (dialogCancelButtonOnRight?CANCEL_BUTTON_ON_RIGHT:0) + + (ignoreRescaleSlope?IGNORE_RESCALE_SLOPE:0) + + (nonBlockingFilterDialogs?NON_BLOCKING_DIALOGS:0) + + (fixedDicomScaling?FIXED_DICOM_SCALING:0); + //+ (calibrateConversions?CALIBRATE_CONVERSIONS:0); + prefs.put(OPTIONS2, Integer.toString(options2)); + } + + /** Saves the Point loc in the preferences + file as a string using the keyword key. */ + public static void saveLocation(String key, Point loc) { + if (!doNotSaveWindowLocations) + set(key, loc!=null?loc.x+","+loc.y:null); + } + + /** Uses the keyword key to retrieve a location + from the preferences file. Returns null if the + key is not found or the location is not valid (e.g., offscreen). */ + public static Point getLocation(String key) { + String value = ijPrefs.getProperty(KEY_PREFIX+key); + if (value==null) return null; + int index = value.indexOf(","); + if (index==-1) return null; + double xloc = Tools.parseDouble(value.substring(0, index)); + if (Double.isNaN(xloc) || index==value.length()-1) return null; + double yloc = Tools.parseDouble(value.substring(index+1)); + if (Double.isNaN(yloc)) return null; + Point p = new Point((int)xloc, (int)yloc); + Rectangle bounds = GUI.getScreenBounds(p); // get bounds of screen that contains p + if (bounds!=null && p.x+100<=bounds.x+bounds.width && p.y+ 40<=bounds.y+bounds.height) { + if (locKeys.get(key)==null) { // first time for this key? + locKeys.setProperty(key, ""); + Rectangle primaryScreen = GUI.getMaxWindowBounds(); + ImageJ ij = IJ.getInstance(); + Point ijLoc = ij!=null?ij.getLocation():null; + //System.out.println("getLoc: "+key+" "+(ijLoc!=null&&primaryScreen.contains(ijLoc)) + " "+!primaryScreen.contains(p)); + if ((ijLoc!=null&&primaryScreen.contains(ijLoc)) && !primaryScreen.contains(p)) + return null; // return null if "ImageJ" window on primary screen and this location is not + } + return p; + } else + return null; + } + + /** Save plugin preferences. */ + static void savePluginPrefs(Properties prefs) { + Enumeration e = ijPrefs.keys(); + while (e.hasMoreElements()) { + String key = (String) e.nextElement(); + if (key.indexOf(KEY_PREFIX) == 0) + prefs.put(key, ijPrefs.getProperty(key)); + } + } + + public static void savePrefs(Properties prefs, String path) throws IOException{ + FileOutputStream fos = new FileOutputStream(path); + BufferedOutputStream bos = new BufferedOutputStream(fos); + prefs.store(bos, "ImageJ "+ImageJ.VERSION+" Preferences"); + bos.close(); + } + + /** Returns the number of threads used by PlugInFilters to process images and stacks. */ + public static int getThreads() { + if (threads==0) { + threads = getInt(THREADS, 0); + int processors = Runtime.getRuntime().availableProcessors(); + if (threads<1 || threads>processors) + threads = processors; + } + return threads; + } + + /** Sets the number of threads (1-32) used by PlugInFilters to process stacks. */ + public static void setThreads(int n) { + if (n<1) n = 1; + threads = n; + } + + /** Sets the transparent index (0-255), or set to -1 to disable transparency. */ + public static void setTransparentIndex(int index) { + if (index<-1 || index>255) index = -1; + transparentIndex = index; + } + + /** Returns the transparent index (0-255), or -1 if transparency is disabled. */ + public static int getTransparentIndex() { + return transparentIndex; + } + + public static Properties getControlPanelProperties() { + return ijPrefs; + } + + public static String defaultResultsExtension() { + return get("options.ext", ".csv"); + } + + /** Sets the GenericDialog and Command Finder text scale (0.5 to 3.0). */ + public static void setGuiScale(double scale) { + if (scale>=0.5 && scale<=3.0) { + guiScale = scale; + set(GUI_SCALE, guiScale); + Roi.resetDefaultHandleSize(); + } + } + + /** Returns the GenericDialog and Command Finder text scale. */ + public static double getGuiScale() { + return guiScale; + } + + /** Returns the custom properties (IJ_Props.txt) file path. */ + public static String getCustomPropsPath() { + return propertiesPath; + } + + /** Returns the custom preferences (IJ_Prefs.txt) file path. */ + public static String getCustomPrefsPath() { + return preferencesPath; + } + + /** Retrieves a string from IJ_Props or IJ_Prefs.txt. + Does not retrieve strings set using Prefs.set(). */ + public static String getString(String key, String defaultString) { + if (props==null) + return defaultString; + String s = props.getProperty(key); + if (s==null) + return defaultString; + else + return s; + } + + /** Retrieves a string from string in IJ_Props or IJ_Prefs.txt. */ + public static String getString(String key) { + return props.getProperty(key); + } + + /** Retrieves a number from IJ_Props or IJ_Prefs.txt. + Does not retrieve numbers set using Prefs.set(). */ + public static int getInt(String key, int defaultValue) { + if (props==null) //workaround for Netscape JIT bug + return defaultValue; + String s = props.getProperty(key); + if (s!=null) { + try { + return Integer.decode(s).intValue(); + } catch (NumberFormatException e) {IJ.log(""+e);} + } + return defaultValue; + } + + /** Retrieves a number from IJ_Props or IJ_Prefs.txt. + Does not retrieve numbers set using Prefs.set(). */ + public static double getDouble(String key, double defaultValue) { + if (props==null) + return defaultValue; + String s = props.getProperty(key); + Double d = null; + if (s!=null) { + try {d = Double.valueOf(s);} + catch (NumberFormatException e){d = null;} + if (d!=null) + return(d.doubleValue()); + } + return defaultValue; + } + + /** Retrieves a boolean from IJ_Props or IJ_Prefs.txt. + Does not retrieve boolean set using Prefs.set(). */ + public static boolean getBoolean(String key, boolean defaultValue) { + if (props==null) return defaultValue; + String s = props.getProperty(key); + if (s==null) + return defaultValue; + else + return s.equals("true"); + } + + /** Finds a color in IJ_Props or IJ_Prefs.txt. */ + public static Color getColor(String key, Color defaultColor) { + int i = getInt(key, 0xaaa); + if (i == 0xaaa) + return defaultColor; + return new Color((i >> 16) & 0xFF, (i >> 8) & 0xFF, i & 0xFF); + } + + public static boolean commandLineMacro() { + return commandLineMacro; + } + +} + diff --git a/mrj/26/ij/WindowManager.java b/mrj/26/ij/WindowManager.java new file mode 100644 index 00000000..ba944c5a --- /dev/null +++ b/mrj/26/ij/WindowManager.java @@ -0,0 +1,657 @@ +package ij; + +import java.awt.CheckboxMenuItem; +import java.awt.Dialog; +import java.awt.Frame; +import java.awt.KeyboardFocusManager; +import java.awt.MenuItem; +import java.awt.Window; +import java.util.ArrayList; +import java.util.Hashtable; +import java.util.Vector; + +import ij.gui.HistogramWindow; +import ij.gui.ImageWindow; +import ij.gui.PlotWindow; +import ij.macro.Interpreter; +import ij.plugin.frame.Commands; +import ij.plugin.frame.Editor; +import ij.plugin.frame.PlugInFrame; +import ij.plugin.frame.Recorder; +import ij.text.TextWindow; +import ij.util.Tools; + +/** This class consists of static methods used to manage ImageJ's windows. */ +public class WindowManager { + + public static boolean checkForDuplicateName; + private static Vector imageList = new Vector(); // list of image windows + private static Vector activations = new Vector(); // list of image windows, ordered by activation time + private static Vector nonImageList = new Vector(); // list of non-image windows (Frames and Dialogs) + private static ImageWindow currentWindow; // active image window + private static Window frontWindow; + private static Window frontTable; + private static Frame frontFrame; + private static Hashtable tempImageTable = new Hashtable(); + + private WindowManager() { + } + + /** Makes the image contained in the specified window the active image. */ + public static void setCurrentWindow(ImageWindow win) { + if (win==null || win.isClosed() || win.getImagePlus()==null) // deadlock-"wait to lock" + return; + //IJ.log("setCurrentWindow: "+win.getImagePlus().getTitle()+" ("+(currentWindow!=null?currentWindow.getImagePlus().getTitle():"null") + ")"); + setWindow(win); + tempImageTable.remove(Thread.currentThread()); + if (win==currentWindow || imageList.size()==0) + return; + if (currentWindow!=null) { + // free up pixel buffers used by current window + ij.ImagePlus imp = currentWindow.getImagePlus(); + if (imp!=null ) { + if (!Prefs.keepUndoBuffers) + imp.trimProcessor(); + imp.saveRoi(); + } + } + ij.Undo.reset(); + currentWindow = win; + activations.remove(win); + activations.add(win); + Menus.updateMenus(); + if (IJ.recording() && !IJ.isMacro()) + Recorder.record("selectImage", win.getImagePlus().getTitle()); + } + + /** Returns the active ImageWindow. */ + public static ImageWindow getCurrentWindow() { + return currentWindow; + } + + static int getCurrentIndex() { + return imageList.indexOf(currentWindow); + } + + /** Returns a reference to the active image or null if there isn't one. + * @see IJ#getImage + */ + public static ij.ImagePlus getCurrentImage() { + ij.ImagePlus img = (ij.ImagePlus)tempImageTable.get(Thread.currentThread()); + //String str = (img==null)?" null":""; + if (img==null) + img = getActiveImage(); + //if (img!=null) IJ.log("getCurrentImage: "+img.getTitle()+" "+Thread.currentThread().hashCode()+str); + return img; + } + + /** Makes the specified image temporarily the active + image for this thread. Call again with a null + argument to revert to the previous active image. */ + public static void setTempCurrentImage(ij.ImagePlus img) { + //IJ.log("setTempImage: "+(img!=null?""+img:"null")+" "+Thread.currentThread().hashCode()); + if (img==null) + tempImageTable.remove(Thread.currentThread()); + else + tempImageTable.put(Thread.currentThread(), img); + } + + /** Sets a temporary image for the specified thread. */ + public static void setTempCurrentImage(Thread thread, ij.ImagePlus img) { + if (thread==null) + throw new RuntimeException("thread==null"); + if (img==null) + tempImageTable.remove(thread); + else + tempImageTable.put(thread, img); + } + + /** Returns the active ImagePlus. */ + private static ij.ImagePlus getActiveImage() { + if (currentWindow!=null) + return currentWindow.getImagePlus(); + else if (frontWindow!=null && (frontWindow instanceof ImageWindow)) + return frontWindow!=null?((ImageWindow)frontWindow).getImagePlus():null; + else if (imageList.size()>0) { + ij.ImagePlus imp = getFocusManagerActiveImage(); + if (imp!=null) + return imp; + ImageWindow win = (ImageWindow)imageList.get(imageList.size()-1); + return win.getImagePlus(); + } else + return Interpreter.getLastBatchModeImage(); + } + + private static ij.ImagePlus getFocusManagerActiveImage() { + if (IJ.isMacro()) + return null; + KeyboardFocusManager kfm = KeyboardFocusManager.getCurrentKeyboardFocusManager(); + Window win = kfm.getActiveWindow(); + ij.ImagePlus imp = null; + if (win!=null && (win instanceof ImageWindow)) + imp = ((ImageWindow)win).getImagePlus(); + return imp; + } + + /** Returns the number of open image windows. */ + public static int getWindowCount() { + int count = imageList.size(); + return count; + } + + /** Returns the number of open images. */ + public static int getImageCount() { + int count = imageList.size(); + count += Interpreter.getBatchModeImageCount(); + if (count==0 && getCurrentImage()!=null) + count = 1; + return count; + } + + /** Returns the front most window or null. */ + public static Window getActiveWindow() { + return frontWindow; + } + + /** Returns the Window containing the active table, or null. + * @see ij.measure.ResultsTable#getActiveTable + */ + public static Window getActiveTable() { + return frontTable; + } + + /** Obsolete; replaced by getActiveWindow. */ + public static Frame getFrontWindow() { + return frontFrame; + } + + /** Returns a list of the IDs of open images. Returns + null if no image windows are open. */ + public synchronized static int[] getIDList() { + int nWindows = imageList.size(); + int[] batchModeImages = Interpreter.getBatchModeImageIDs(); + int nBatchImages = batchModeImages.length; + if ((nWindows+nBatchImages)==0) + return null; + int[] list = new int[nWindows+nBatchImages]; + for (int i=0; i0) + imageID = getNthImageID(imageID); + if (imageID==0 || getImageCount()==0) + return null; + ij.ImagePlus imp2 = Interpreter.getBatchModeImage(imageID); + if (imp2!=null) + return imp2; + ij.ImagePlus imp = null; + for (int i=0; ilist.length) + return 0; + else + return list[n-1]; + } else { + if (n>imageList.size()) return 0; + ImageWindow win = (ImageWindow)imageList.get(n-1); + if (win!=null) + return win.getImagePlus().getID(); + else + return 0; + } + } + + + /** Returns the first image that has the specified title or null if it is not found. */ + public synchronized static ij.ImagePlus getImage(String title) { + int[] wList = getIDList(); + if (wList==null) return null; + for (int i=0; i=0) { + Menus.removeWindowMenuItem(index); + nonImageList.removeElement(win); + } + if (win!=null && win==frontTable) + frontTable = null; + } + setWindow(null); + } + + /** Removes the specified Frame from the Window menu. */ + public static void removeWindow(Frame win) { + removeWindow((Window)win); + } + + private static void removeImageWindow(ImageWindow win) { + int index = imageList.indexOf(win); + if (index==-1) + return; // not on the window list + try { + synchronized(WindowManager.class) { + imageList.remove(win); + } + activations.remove(win); + if (imageList.size()>1 && !Prefs.closingAll) { + ImageWindow win2 = activations.size()>0?(ImageWindow)activations.get(activations.size()-1):null; + setCurrentWindow(win2); + } else + currentWindow = null; + setTempCurrentImage(null); //??? + int nonImageCount = nonImageList.size(); + if (nonImageCount>0) + nonImageCount++; + Menus.removeWindowMenuItem(nonImageCount+index); + Menus.updateMenus(); + ij.Undo.reset(); + } catch (Exception e) { } + } + + /** The specified Window becomes the front window. */ + public static void setWindow(Window win) { + //System.out.println("setWindow(W): "+win); + frontWindow = win; + if (win instanceof Frame) + frontFrame = (Frame)win; + } + + /** The specified frame becomes the front window, the one returnd by getFrontWindow(). */ + public static void setWindow(Frame win) { + frontWindow = win; + frontFrame = win; + if (win!=null && win instanceof TextWindow && !(win instanceof Editor) && !"Log".equals(((TextWindow)win).getTitle())) + frontTable = win; + //System.out.println("Set window(F): "+(win!=null?win.getTitle():"null")); + } + + /** Closes all windows. Stops and returns false if an image or Editor "save changes" dialog is canceled. */ + public synchronized static boolean closeAllWindows() { + Prefs.closingAll = true; + while (imageList.size()>0) { + if (!((ImageWindow)imageList.get(0)).close()) { + Prefs.closingAll = false; + return false; + } + if (!quittingViaMacro()) + IJ.wait(100); + } + Prefs.closingAll = false; + Frame[] nonImages = getNonImageWindows(); + for (int i=0; i0) // remove image size (e.g., " 90K") + menuItemLabel = menuItemLabel.substring(0, lastSpace); + String idString = item.getActionCommand(); + int id = (int)Tools.parseDouble(idString, 0); + ij.ImagePlus imp = WindowManager.getImage(id); + if (imp==null) return; + ImageWindow win1 = imp.getWindow(); + if (win1==null) return; + setCurrentWindow(win1); + toFront(win1); + int index = imageList.indexOf(win1); + int n = Menus.window.getItemCount(); + int start = Menus.WINDOW_MENU_ITEMS+Menus.windowMenuItems2; + for (int j=start; j1.0) { + TEXT_GAP = (int)(TEXT_GAP*SCALE); + textGap = centerOnScreen?0:TEXT_GAP; + } + if (Prefs.blackCanvas && getClass().getName().equals("ij.gui.ImageWindow")) { + setForeground(Color.white); + setBackground(Color.black); + } else { + setForeground(Color.black); + if (IJ.isLinux()) + setBackground(ImageJ.backgroundColor); + else + setBackground(Color.white); + } + boolean openAsHyperStack = imp.getOpenAsHyperStack(); + ij = IJ.getInstance(); + this.imp = imp; + if (ic==null) { + ic = (this instanceof ij.gui.PlotWindow) ? new ij.gui.PlotCanvas(imp) : new ij.gui.ImageCanvas(imp); + newCanvas=true; + } + this.ic = ic; + ImageWindow previousWindow = imp.getWindow(); + setLayout(new ij.gui.ImageLayout(ic)); + add(ic); + addFocusListener(this); + addWindowListener(this); + addWindowStateListener(this); + addKeyListener(ij); + setFocusTraversalKeysEnabled(false); + if (!(this instanceof ij.gui.StackWindow)) + addMouseWheelListener(this); + setResizable(true); + if (!(this instanceof ij.gui.HistogramWindow &&IJ.isMacro()&&Interpreter.isBatchMode())) { + WindowManager.addWindow(this); + imp.setWindow(this); + } + if (previousWindow!=null) { + if (newCanvas) + setLocationAndSize(false); + else + ic.update(previousWindow.getCanvas()); + Point loc = previousWindow.getLocation(); + setLocation(loc.x, loc.y); + if (!(this instanceof ij.gui.StackWindow || this instanceof ij.gui.PlotWindow)) { //layout now unless components will be added later + pack(); + if (IJ.isMacro()) + imp.setDeactivated(); //prepare for waitTillActivated (imp may have been activated before if it gets a new Window now) + show(); + } + if (ic.getMagnification()!=0.0) + imp.setTitle(imp.getTitle()); + boolean unlocked = imp.lockSilently(); + boolean changes = imp.changes; + imp.changes = false; + previousWindow.close(); + imp.changes = changes; + if (unlocked) + imp.unlock(); + if (this.imp!=null) + this.imp.setOpenAsHyperStack(openAsHyperStack); + WindowManager.setCurrentWindow(this); + } else { + setLocationAndSize(false); + if (ij!=null && !IJ.isMacintosh()) { + Image img = ij.getIconImage(); + if (img!=null) try { + setIconImage(img); + } catch (Exception e) {} + } + if (nextLocation!=null) + setLocation(nextLocation); + else if (centerOnScreen) + GUI.center(this); + nextLocation = null; + centerOnScreen = false; + if (Interpreter.isBatchMode() || (IJ.getInstance()==null&&this instanceof ij.gui.HistogramWindow)) { + WindowManager.setTempCurrentImage(imp); + Interpreter.addBatchModeImage(imp); + } else { + if (IJ.isMacro()) + imp.setDeactivated(); //prepare for waitTillActivated (imp may have been activated previously and gets a new Window now) + show(); + } + } + } + + private void setLocationAndSize(boolean updating) { + if (imp==null) + return; + int width = imp.getWidth(); + int height = imp.getHeight(); + + // load preferences file location + Point loc = Prefs.getLocation(LOC_KEY); + Rectangle bounds = null; + if (loc!=null) { + bounds = GUI.getMaxWindowBounds(loc); + if (bounds!=null && (loc.x>bounds.x+bounds.width/3||loc.y>bounds.y+bounds.height/3) + && (loc.x+width>bounds.x+bounds.width||loc.y+height>bounds.y+bounds.height)) { + loc = null; + bounds = null; + } + } + // if loc not valid, use screen bounds of visible window (this) or of main window (ij) if not visible yet (updating == false) + Rectangle maxWindow = bounds!=null?bounds: GUI.getMaxWindowBounds(updating?this: ij); + + if (WindowManager.getWindowCount()<=1) + xbase = -1; + if (width>maxWindow.width/2 && xbase>maxWindow.x+5+XINC*6) + xbase = -1; + if (xbase==-1) { + count = 0; + if (loc!=null) { + xbase = loc.x; + ybase = loc.y; + } else if (ij!=null) { + Rectangle ijBounds = ij.getBounds(); + if (ijBounds.y-maxWindow.xmaxWindow.x+maxWindow.width) { + xbase = maxWindow.x+maxWindow.width - width - 10; + if (xbasescreenWidth || ybase+height*mag>=screenHeight) { + double mag2 = ImageCanvas.getLowerZoomLevel(mag); + if (mag2==mag) break; + mag = mag2; + } + } + + if (mag<1.0) { + initialMagnification = mag; + ic.setSize((int)(width*mag), (int)(height*mag)); + } + ic.setMagnification(mag); + if (y+height*mag>screenHeight) + y = ybase; + if (Prefs.open100Percent && ic.getMagnification()<1.0) { + while(ic.getMagnification()<1.0) + ic.zoomIn(0, 0); + setSize(Math.min(width, screenWidth-x), Math.min(height, screenHeight-y)); + validate(); + } else + pack(); + if (!updating) + setLocation(x, y); + } + + Rectangle getMaxWindow(int xloc, int yloc) { + return GUI.getMaxWindowBounds(new Point(xloc, yloc)); + } + + public double getInitialMagnification() { + return initialMagnification; + } + + public Insets getInsets() { + return getInsets(true); + } + + /** Override Container getInsets() to make room for some text above the image. + * With "includeSmallImageMargins", also includes the margins for padding an image + * that is too small for the window size. */ + public Insets getInsets(boolean includeSmallImageMargins) { + Insets insets = super.getInsets(); + if (imp==null) + return insets; + double mag = ic.getMagnification(); + int extraWidth = 0, extraHeight = 0; + if (includeSmallImageMargins) { + extraWidth = (int)((MIN_WIDTH - imp.getWidth()*mag)/2.0); + if (extraWidth<0) extraWidth = 0; + extraHeight = (int)((MIN_HEIGHT - imp.getHeight()*mag)/2.0); + if (extraHeight<0) extraHeight = 0; + } + insets = new Insets(insets.top+textGap+extraHeight, insets.left+extraWidth, insets.bottom+extraHeight, insets.right+extraWidth); + return insets; + } + + /** Draws the subtitle. */ + public void drawInfo(Graphics g) { + if (imp==null) + return; + if (textGap!=0) { + Insets insets = super.getInsets(); + Color savec = null; + if (imp.isComposite()) { + CompositeImage ci = (CompositeImage)imp; + if (ci.getMode()==IJ.COMPOSITE) { + savec = g.getColor(); + Color c = ci.getChannelColor(); + if (Color.green.equals(c)) + c = new Color(0,180,0); + g.setColor(c); + } + } + Java2.setAntialiasedText(g, true); + if (SCALE>1.0) { + Font font = new Font("SansSerif", Font.PLAIN, (int)(12*SCALE)); + g.setFont(font); + } + g.drawString(createSubtitle(), insets.left+5, insets.top+TEXT_GAP); + if (savec!=null) + g.setColor(savec); + } + } + + /** Creates the subtitle. */ + public String createSubtitle() { + String s=""; + if (imp==null) + return s; + int stackSize = imp.getStackSize(); + if (stackSize>1) { + ImageStack stack = imp.getStack(); + int currentSlice = imp.getCurrentSlice(); + s += currentSlice+"/"+stackSize; + String label = stack.getShortSliceLabel(currentSlice); + if (label!=null && label.length()>0) { + if (imp.isHyperStack()) label = label.replace(';', ' '); + s += " (" + label + ")"; + } + if ((this instanceof ij.gui.StackWindow) && running2) { + return s; + } + s += "; "; + } else { + String label = imp.getProp("Slice_Label"); + if (label==null && imp.hasImageStack()) + label = imp.getStack().getSliceLabel(1); + if (label!=null && label.length()>0) { + int newline = label.indexOf('\n'); + if (newline>0) + label = label.substring(0, newline); + int len = label.length(); + if (len>4 && label.charAt(len-4)=='.' && !Character.isDigit(label.charAt(len-1))) + label = label.substring(0,len-4); + if (label.length()>60) + label = label.substring(0, 60)+"..."; + s = "\""+label + "\"; "; + } + } + int type = imp.getType(); + Calibration cal = imp.getCalibration(); + if (cal.scaled()) { + boolean unitsMatch = cal.getXUnit().equals(cal.getYUnit()); + double cwidth = imp.getWidth()*cal.pixelWidth; + double cheight = imp.getHeight()*cal.pixelHeight; + int digits = Tools.getDecimalPlaces(cwidth, cheight); + if (digits>2) digits=2; + if (unitsMatch) { + s += IJ.d2s(cwidth,digits) + "x" + IJ.d2s(cheight,digits) + + " " + cal.getUnits() + " (" + imp.getWidth() + "x" + imp.getHeight() + "); "; + } else { + s += d2s(cwidth) + " " + cal.getXUnit() + " x " + + d2s(cheight) + " " + cal.getYUnit() + + " (" + imp.getWidth() + "x" + imp.getHeight() + "); "; + } + } else + s += imp.getWidth() + "x" + imp.getHeight() + " pixels; "; + switch (type) { + case ImagePlus.GRAY8: + case ImagePlus.COLOR_256: + s += "8-bit"; + break; + case ImagePlus.GRAY16: + s += "16-bit"; + break; + case ImagePlus.GRAY32: + s += "32-bit"; + break; + case ImagePlus.COLOR_RGB: + s += imp.isRGB() ? "RGB" : "32-bit (int)"; + break; + } + if (imp.isInvertedLut()) + s += " (inverting LUT)"; + return s+"; "+getImageSize(imp); + } + + public static String getImageSize(ImagePlus imp) { + if (imp==null) + return null; + double size = imp.getSizeInBytes()/1024.0; + String s2=null, s3=null; + if (size<1024.0) + {s2=IJ.d2s(size,0); s3="K";} + else if (size<10000.0) + {s2=IJ.d2s(size/1024.0,1); s3="MB";} + else if (size<1048576.0) + {s2=IJ.d2s(Math.round(size/1024.0),0); s3="MB";} + else + {s2=IJ.d2s(size/1048576.0,1); s3="GB";} + if (s2.endsWith(".0")) s2 = s2.substring(0, s2.length()-2); + return s2+s3; + } + + private String d2s(double n) { + int digits = Tools.getDecimalPlaces(n); + if (digits>2) digits=2; + return IJ.d2s(n,digits); + } + + public void paint(Graphics g) { + drawInfo(g); + Rectangle r = ic.getBounds(); + int extraWidth = MIN_WIDTH - r.width; + int extraHeight = MIN_HEIGHT - r.height; + if (extraWidth<=0 && extraHeight<=0 && !Prefs.noBorder && !IJ.isLinux()) + g.drawRect(r.x-1, r.y-1, r.width+1, r.height+1); + } + + /** Removes this window from the window list and disposes of it. + Returns false if the user cancels the "save changes" dialog. */ + public boolean close() { + boolean isRunning = running || running2; + running = running2 = false; + if (imp==null) return true; + boolean virtual = imp.getStackSize()>1 && imp.getStack().isVirtual(); + if (isRunning) IJ.wait(500); + if (imp==null) return true; + boolean changes = imp.changes; + ij.gui.Roi roi = imp.getRoi(); + if (roi!=null && (roi instanceof ij.gui.PointRoi) && ((ij.gui.PointRoi)roi).promptBeforeDeleting()) + changes = true; + if (ij==null || ij.quittingViaMacro() || IJ.getApplet()!=null || Interpreter.isBatchMode() || IJ.macroRunning() || virtual) + changes = false; + if (changes) { + String msg; + String name = imp.getTitle(); + if (name.length()>22) + msg = "Save changes to\n" + "\"" + name + "\"?"; + else + msg = "Save changes to \"" + name + "\"?"; + if (imp.isLocked()) + msg += "\nWARNING: This image is locked.\nProbably, processing is unfinished (slow or still previewing)."; + toFront(); + ij.gui.YesNoCancelDialog d = new ij.gui.YesNoCancelDialog(this, "ImageJ", msg); + if (d.cancelPressed()) + return false; + else if (d.yesPressed()) { + FileSaver fs = new FileSaver(imp); + if (!fs.save()) return false; + } + } + closed = true; + if (WindowManager.getWindowCount()==0) { + xloc = 0; + yloc = 0; + } + WindowManager.removeWindow(this); + if (ij!=null && ij.quitting()) // this may help avoid thread deadlocks + return true; + Rectangle bounds = getBounds(); + if (!IJ.isMacro() + && bounds.y0) + sw.removeScrollbars(); + else if (stackSize>1 && nScrollbars==0) + sw.addScrollbars(imp); + } + pack(); + repaint(); + maxBounds = getMaximumBounds(); + setMaximizedBounds(maxBounds); + setMaxBoundsTime = System.currentTimeMillis(); + } + + public ij.gui.ImageCanvas getCanvas() { + return ic; + } + + + static ImagePlus getClipboard() { + return ImagePlus.getClipboard(); + } + + public Rectangle getMaximumBounds() { + Rectangle maxWindow = GUI.getMaxWindowBounds(this); + if (imp==null) + return maxWindow; + double width = imp.getWidth(); + double height = imp.getHeight(); + double iAspectRatio = width/height; + maxWindowBounds = maxWindow; + if (iAspectRatio/((double)maxWindow.width/maxWindow.height)>0.75) { + maxWindow.y += 22; // uncover ImageJ menu bar + maxWindow.height -= 22; + } + Dimension extraSize = getExtraSize(); + double maxWidth = maxWindow.width-extraSize.width; + double maxHeight = maxWindow.height-extraSize.height; + double mAspectRatio = maxWidth/maxHeight; + int wWidth, wHeight; + double mag; + if (iAspectRatio>=mAspectRatio) { + mag = maxWidth/width; + wWidth = maxWindow.width; + wHeight = (int)(height*mag+extraSize.height); + } else { + mag = maxHeight/height; + wHeight = maxWindow.height; + wWidth = (int)(width*mag+extraSize.width); + } + int xloc = (int)(maxWidth-wWidth)/2; + if (xlocmaxWindow.x+maxWindow.width-wWidth) + xloc = maxWindow.x; + wWidth = Math.min(wWidth, maxWindow.x-xloc+maxWindow.width); + wHeight = Math.min(wHeight, maxWindow.height); + return new Rectangle(xloc, maxWindow.y, wWidth, wHeight); + } + + Dimension getExtraSize() { + Insets insets = getInsets(); + int extraWidth = insets.left+insets.right + 10; + int extraHeight = insets.top+insets.bottom + 10; + if (extraHeight==20) extraHeight = 42; + int members = getComponentCount(); + for (int i=1; iwidth) srcRect.x = width-srcRect.width; + } else { + srcRect.y += rotation*amount*Math.max(height/200, 1); + if (srcRect.y<0) srcRect.y = 0; + if (srcRect.y+srcRect.height>height) srcRect.y = height-srcRect.height; + } + if (srcRect.x!=xstart || srcRect.y!=ystart) + ic.repaint(); + } + + /** Copies the current ROI to the clipboard. The entire + image is copied if there is no ROI. */ + public void copy(boolean cut) { + imp.copy(cut); + } + + + public void paste() { + imp.paste(); + } + + /** This method is called by ImageCanvas.mouseMoved(MouseEvent). + @see ij.gui.ImageCanvas#mouseMoved + */ + public void mouseMoved(int x, int y) { + imp.mouseMoved(x, y); + } + + public String toString() { + return imp!=null?imp.getTitle():""; + } + + /** Causes the next image to be opened to be centered on the screen + and displayed without informational text above the image. */ + public static void centerNextImage() { + centerOnScreen = true; + } + + /** Causes the next image to be displayed at the specified location. */ + public static void setNextLocation(Point loc) { + nextLocation = loc; + } + + /** Causes the next image to be displayed at the specified location. */ + public static void setNextLocation(int x, int y) { + nextLocation = new Point(x, y); + } + + /** Moves and resizes this window. Changes the + magnification so the image fills the window. */ + public void setLocationAndSize(int x, int y, int width, int height) { + setBounds(x, y, width, height); + getCanvas().fitToWindow(); + pack(); + } + + @Override + public void setLocation(int x, int y) { + super.setLocation(x, y); + } + + public void setSliderHeight(int height) { + sliderHeight = height; + } + + public int getSliderHeight() { + return sliderHeight; + } + + public static void setImageJMenuBar(ImageWindow win) { + ImageJ ij = IJ.getInstance(); + boolean setMenuBar = true; + ImagePlus imp = win.getImagePlus(); + if (imp!=null) + setMenuBar = imp.setIJMenuBar(); + MenuBar mb = Menus.getMenuBar(); + if (mb!=null && mb==win.getMenuBar()) + setMenuBar = false; + setMenuBarTime = 0L; + if (setMenuBar && ij!=null && !ij.quitting()) { + IJ.wait(10); // may be needed for Java 1.4 on OS X + long t0 = System.currentTimeMillis(); + win.setMenuBar(mb); + long time = System.currentTimeMillis()-t0; + setMenuBarTime = time; + Menus.setMenuBarCount++; + if (time>2000L) + Prefs.setIJMenuBar = false; + } + if (imp!=null) imp.setIJMenuBar(true); + } + +} //class ImageWindow diff --git a/mrj/26/ij/gui/Toolbar.java b/mrj/26/ij/gui/Toolbar.java new file mode 100644 index 00000000..fc5b58bf --- /dev/null +++ b/mrj/26/ij/gui/Toolbar.java @@ -0,0 +1,2166 @@ +package ij.gui; + +import java.awt.BasicStroke; +import java.awt.Canvas; +import java.awt.CheckboxMenuItem; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Event; +import java.awt.Font; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Menu; +import java.awt.MenuBar; +import java.awt.MenuItem; +import java.awt.Polygon; +import java.awt.PopupMenu; +import java.awt.RenderingHints; +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.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.awt.event.MouseMotionListener; +import java.awt.image.BufferedImage; +import java.io.File; +import java.util.Arrays; +import java.util.Hashtable; +import java.util.Locale; +import java.util.Timer; +import java.util.TimerTask; + +import javax.imageio.ImageIO; + +import ij.IJ; +import ij.IJEventListener; +import ij.ImageJ; +import ij.ImagePlus; +import ij.Menus; +import ij.Prefs; +import ij.WindowManager; +import ij.macro.Program; +import ij.plugin.MacroInstaller; +import ij.plugin.frame.*; +import ij.plugin.tool.MacroToolRunner; +import ij.plugin.tool.PlugInTool; + +/** The ImageJ toolbar. */ +public class Toolbar extends Canvas implements MouseListener, MouseMotionListener, ItemListener, ActionListener { + + public static final int RECTANGLE = 0; + public static final int OVAL = 1; + public static final int POLYGON = 2; + public static final int FREEROI = 3; + public static final int LINE = 4; + public static final int POLYLINE = 5; + public static final int FREELINE = 6; + public static final int POINT = 7, CROSSHAIR = 7; + public static final int WAND = 8; + public static final int TEXT = 9; + public static final int UNUSED = 10; + public static final int MAGNIFIER = 11; + public static final int HAND = 12; + public static final int DROPPER = 13; + public static final int ANGLE = 14; + public static final int CUSTOM1 = 15; + public static final int CUSTOM2 = 16; + public static final int CUSTOM3 = 17; + public static final int CUSTOM4 = 18; + public static final int CUSTOM5 = 19; + public static final int CUSTOM6 = 20; + public static final int CUSTOM7 = 21; + + public static final int DOUBLE_CLICK_THRESHOLD = 650; //ms + + public static final int RECT_ROI=0, ROUNDED_RECT_ROI=1, ROTATED_RECT_ROI=2; + public static final int OVAL_ROI=0, ELLIPSE_ROI=1, BRUSH_ROI=2; + + public static final String[] builtInTools = {"Arrow","Brush","Command Finder", "Developer Menu","Flood Filler", + "Label Maker","LUT Menu","Overlay Brush","Pencil","Pixel Inspector","Selection Rotator", + "Spray Can","Stacks Menu","ROI Menu"}; + private static final String[] builtInTools2 = {"Pixel Inspection Tool","Paintbrush Tool","Flood Fill Tool"}; + + private static final int NUM_TOOLS = 23; + private static final int MAX_EXTRA_TOOLS = 8; + private static final int MAX_TOOLS = NUM_TOOLS+MAX_EXTRA_TOOLS; + private static final int NUM_BUTTONS = 21; + private static final int BUTTON_WIDTH = 30; + private static final int BUTTON_HEIGHT = 31; + private static final int SIZE = 28; // no longer used + private static final int GAP_SIZE = 9; + private static final int OFFSET = 7; + private static final String BRUSH_SIZE = "toolbar.brush.size"; + public static final String CORNER_DIAMETER = "toolbar.arc.size"; + public static String TOOL_KEY = "toolbar.tool"; + + private Dimension ps; + private boolean[] down; + private static int current; + private int previousTool; + private int x,y; + private int xOffset, yOffset; + private long mouseDownTime; + private Graphics g; + private static Toolbar instance; + private int mpPrevious = RECTANGLE; + private String[] names = new String[MAX_TOOLS]; + private String[] icons = new String[MAX_TOOLS]; + private PlugInTool[] tools = new PlugInTool[MAX_TOOLS]; + private PopupMenu[] menus = new PopupMenu[MAX_TOOLS]; + private int nExtraTools; + private MacroInstaller macroInstaller; + private boolean addingSingleTool; + private boolean installingStartupTool; + private boolean doNotSavePrefs; + private int pc; + private String icon; + private int startupTime; + private PopupMenu rectPopup, ovalPopup, pointPopup, linePopup, zoomPopup, pickerPopup, switchPopup; + private CheckboxMenuItem rectItem, roundRectItem, rotatedRectItem; + private CheckboxMenuItem ovalItem, ellipseItem, brushItem; + private CheckboxMenuItem pointItem, multiPointItem; + private CheckboxMenuItem straightLineItem, polyLineItem, freeLineItem, arrowItem; + private String currentSet = "Startup Macros"; + private Timer pressTimer; + private static int longClickDelay = 600; //ms + private boolean disableRecording; + + private static Color foregroundColor = Prefs.getColor(Prefs.FCOLOR,Color.white); + private static Color backgroundColor = Prefs.getColor(Prefs.BCOLOR,Color.black); + private static double foregroundValue = Double.NaN; + private static double backgroundValue = Double.NaN; + private static int ovalType = OVAL_ROI; + private static int rectType = RECT_ROI; + private static boolean multiPointMode = Prefs.multiPointMode; + private static boolean arrowMode; + private static int brushSize = (int)Prefs.get(BRUSH_SIZE, 15); + private static int arcSize = (int)Prefs.get(CORNER_DIAMETER, 20); + private int lineType = LINE; + private static boolean legacyMode; + private static double dscale = 1.0; + private static int scale = 1; + private static int buttonWidth; + private static int buttonHeight; + private static int gapSize; + private static int offset; + + private Color gray = new Color(228,228,228); + private Color brighter = gray.brighter(); + private Color darker = new Color(180, 180, 180); + private Color evenDarker = new Color(110, 110, 110); + private Color triangleColor = new Color(150, 0, 0); + private Color toolColor = new Color(0, 25, 45); + + /** Obsolete public constants */ + public static final int SPARE1=UNUSED, SPARE2=CUSTOM1, SPARE3=CUSTOM2, SPARE4=CUSTOM3, + SPARE5=CUSTOM4, SPARE6=CUSTOM5, SPARE7=CUSTOM6, SPARE8=CUSTOM7, SPARE9=22; + + + public Toolbar() { + init(); + down = new boolean[MAX_TOOLS]; + resetButtons(); + down[0] = true; + setForeground(Color.black); + setBackground(ImageJ.backgroundColor); + addMouseListener(this); + addMouseMotionListener(this); + instance = this; + names[getNumTools()-1] = "\"More Tools\" menu (switch toolsets or add tools)"; + icons[getNumTools()-1] = "C900T0d18>T6d18>"; // ">>" + addPopupMenus(); + } + + public void init() { + dscale = Prefs.getGuiScale(); + scale = (int)Math.round(dscale); + if ((dscale>=1.5&&dscale<2.0) || (dscale>=2.5&&dscale<3.0)) + dscale = scale; + if (dscale>1.0) { + buttonWidth = (int)((BUTTON_WIDTH-2)*dscale); + buttonHeight = (int)((BUTTON_HEIGHT-2)*dscale); + offset = (int)Math.round((OFFSET-1)*dscale); + } else { + buttonWidth = BUTTON_WIDTH; + buttonHeight = BUTTON_HEIGHT; + offset = OFFSET; + } + gapSize = GAP_SIZE; + ps = new Dimension(buttonWidth*NUM_BUTTONS-(buttonWidth-gapSize), buttonHeight); + } + + void addPopupMenus() { + rectPopup = newPopupMenu(); + rectItem = new CheckboxMenuItem("Rectangle", rectType==RECT_ROI); + rectItem.addItemListener(this); + rectPopup.add(rectItem); + roundRectItem = new CheckboxMenuItem("Rounded Rectangle", rectType==ROUNDED_RECT_ROI); + roundRectItem.addItemListener(this); + rectPopup.add(roundRectItem); + rotatedRectItem = new CheckboxMenuItem("Rotated Rectangle", rectType==ROTATED_RECT_ROI); + rotatedRectItem.addItemListener(this); + rectPopup.add(rotatedRectItem); + add(rectPopup); + + ovalPopup = newPopupMenu(); + ovalItem = new CheckboxMenuItem("Oval selections", ovalType==OVAL_ROI); + ovalItem.addItemListener(this); + ovalPopup.add(ovalItem); + ellipseItem = new CheckboxMenuItem("Elliptical selections", ovalType==ELLIPSE_ROI); + ellipseItem.addItemListener(this); + ovalPopup.add(ellipseItem); + brushItem = new CheckboxMenuItem("Selection Brush Tool", ovalType==BRUSH_ROI); + brushItem.addItemListener(this); + ovalPopup.add(brushItem); + add(ovalPopup); + + pointPopup = newPopupMenu(); + pointItem = new CheckboxMenuItem("Point Tool", !multiPointMode); + pointItem.addItemListener(this); + pointPopup.add(pointItem); + multiPointItem = new CheckboxMenuItem("Multi-point Tool", multiPointMode); + multiPointItem.addItemListener(this); + pointPopup.add(multiPointItem); + add(pointPopup); + + linePopup = newPopupMenu(); + straightLineItem = new CheckboxMenuItem("Straight Line", lineType==LINE&&!arrowMode); + straightLineItem.addItemListener(this); + linePopup.add(straightLineItem); + polyLineItem = new CheckboxMenuItem("Segmented Line", lineType==POLYLINE); + polyLineItem.addItemListener(this); + linePopup.add(polyLineItem); + freeLineItem = new CheckboxMenuItem("Freehand Line", lineType==FREELINE); + freeLineItem.addItemListener(this); + linePopup.add(freeLineItem); + arrowItem = new CheckboxMenuItem("Arrow tool", lineType==LINE&&!arrowMode); + arrowItem.addItemListener(this); + linePopup.add(arrowItem); + add(linePopup); + + zoomPopup = newPopupMenu(); + addMenuItem(zoomPopup, "Reset Zoom"); + addMenuItem(zoomPopup, "Zoom In"); + addMenuItem(zoomPopup, "Zoom Out"); + addMenuItem(zoomPopup, "View 100%"); + addMenuItem(zoomPopup, "Zoom To Selection"); + addMenuItem(zoomPopup, "Scale to Fit"); + addMenuItem(zoomPopup, "Set..."); + addMenuItem(zoomPopup, "Maximize"); + add(zoomPopup); + + pickerPopup = newPopupMenu(); + addMenuItem(pickerPopup, "White/Black"); + addMenuItem(pickerPopup, "Black/White"); + addMenuItem(pickerPopup, "Red"); + addMenuItem(pickerPopup, "Green"); + addMenuItem(pickerPopup, "Blue"); + addMenuItem(pickerPopup, "Yellow"); + addMenuItem(pickerPopup, "Cyan"); + addMenuItem(pickerPopup, "Magenta"); + pickerPopup.addSeparator(); + addMenuItem(pickerPopup, "Foreground..."); + addMenuItem(pickerPopup, "Background..."); + addMenuItem(pickerPopup, "Colors..."); + addMenuItem(pickerPopup, "Color Picker..."); + add(pickerPopup); + + switchPopup = newPopupMenu(); + add(switchPopup); + } + + private PopupMenu newPopupMenu() { + PopupMenu popup = new PopupMenu(); + ij.gui.GUI.scalePopupMenu(popup); + //System.out.println("newPopupMenu: "+popup.getFont()); + return popup; + } + + private void addMenuItem(PopupMenu menu, String command) { + MenuItem item = new MenuItem(command); + item.addActionListener(this); + menu.add(item); + } + + /** Returns the ID of the current tool (Toolbar.RECTANGLE, Toolbar.OVAL, etc.). */ + public static int getToolId() { + int id = current; + if (legacyMode) { + if (id==CUSTOM1) + id=UNUSED; + else if (id>=CUSTOM2) + id--; + } + return id; + } + + /** Returns the ID of the tool whose name (the description displayed in the status bar) + starts with the specified string, or -1 if the tool is not found. */ + public int getToolId(String name) { + int tool = -1; + for (int i=0; i1.0) + g2d.setStroke(new BasicStroke(IJ.isMacOSX()?1.4f:1.25f)); + } else + g2d.setStroke(new BasicStroke(scale)); + } + + private void fill3DRect(Graphics g, int x, int y, int width, int height, boolean raised) { + if (null==g) return; + if (raised) + g.setColor(gray); + else + g.setColor(darker); + g.fillRect(x+1, y+1, width-2, height-2); + g.setColor(raised ? brighter : evenDarker); + g.drawLine(x, y, x, y + height - 1); + g.drawLine(x + 1, y, x + width - 2, y); + g.setColor(raised ? evenDarker : brighter); + g.drawLine(x + 1, y + height - 1, x + width - 1, y + height - 1); + g.drawLine(x + width - 1, y, x + width - 1, y + height - 2); + } + + private void drawButton(Graphics g, int tool) { + if (g==null) return; + if (legacyMode) { + if (tool==UNUSED) + tool = CUSTOM1; + else if (tool>=CUSTOM1) + tool++; + if ((tool==POLYLINE && lineType!=POLYLINE) || (tool==FREELINE && lineType!=FREELINE)) + return; + } + int index = toolIndex(tool); + int x = index*buttonWidth + 1*scale; + if (tool>=CUSTOM1) + x -= buttonWidth-gapSize; + if (tool!=UNUSED) + fill3DRect(g, x, 1, buttonWidth, buttonHeight-1, !down[tool]); + g.setColor(toolColor); + x = index*buttonWidth + offset; + if (tool>=CUSTOM1) + x -= buttonWidth-gapSize; + int y = offset; + if (dscale==1.3) {x++; y++;} + if (dscale==1.4) {x+=2; y+=2;} + if (down[tool]) { x++; y++;} + this.g = g; + if (tool>=CUSTOM1 && tool<=getNumTools() && icons[tool]!=null) { + drawIcon(g, tool, x+1*scale, y+1*scale); + return; + } + switch (tool) { + case RECTANGLE: + xOffset = x; yOffset = y; + if (rectType==ROUNDED_RECT_ROI) + g.drawRoundRect(x-1*scale, y+1*scale, 17*scale, 13*scale, 8*scale, 8*scale); + else if (rectType==ROTATED_RECT_ROI) + polyline(0,10,7,0,15,6,8,16,0,10); + else + g.drawRect(x-1*scale, y+1*scale, 17*scale, 13*scale); + drawTriangle(16,16); + return; + case OVAL: + xOffset = x; yOffset = y; + if (ovalType==BRUSH_ROI) { + yOffset = y - 1; + polyline(6,4,8,2,12,1,15,2,16,4,15,7,12,8,9,11,9,14,6,16,2,16,0,13,1,10,4,9,6,7,6,4); + } else if (ovalType==ELLIPSE_ROI) { + xOffset = x - 1; + yOffset = y + 1; + polyline(11,0,13,0,14,1,15,1,16,2,17,3,17,7,12,12,11,12,10,13,8,13,7,14,4,14,3,13,2,13,1,12,1,11,0,10,0,9,1,8,1,7,6,2,7,2,8,1,10,1,11,0); + } else + g.drawOval(x, y+1*scale, 17*scale, 13*scale); + drawTriangle(16,16); + return; + case POLYGON: + xOffset = x+1; yOffset = y+2; + polyline(4,0,15,0,15,1,11,5,11,6,14,11,14,12,0,12,0,4,4,0); + return; + case FREEROI: + xOffset = x; yOffset = y+3; + polyline(2,0,5,0,7,3,10,3,12,0,15,0,17,2,17,5,16,8,13,10,11,11,6,11,4,10,1,8,0,6,0,2,2,0); + return; + case LINE: + if (arrowMode) { + xOffset = x; yOffset = y; + m(1,14); d(14,1); m(6,5); d(14,1); m(10,9); d(14,1); m(6,5); d(10,9); + } else { + xOffset = x-1; yOffset = y-1; + m(1,17); d(18,0); + drawDot(0,16); drawDot(17,0); + } + drawTriangle(16,16); + return; + case POLYLINE: + xOffset = x; yOffset = y; + polyline(15,6,11,2,1,2,1,3,7,9,2,14); + drawTriangle(14,16); + return; + case FREELINE: + xOffset = x; yOffset = y; + polyline(16,4,14,6,12,6,9,3,8,3,6,7,2,11,1,11); + drawTriangle(14,16); + return; + case POINT: + xOffset = x; yOffset = y; + if (multiPointMode) { + drawPoint(1,3); drawPoint(9,0); drawPoint(15,5); + drawPoint(10,11); drawPoint(2,13); + } else { + m(1,8); d(6,8); d(6,6); d(10,6); d(10,10); d(6,10); d(6,9); + m(8,1); d(8,5); m(11,8); d(15,8); m(8,11); d(8,15); + m(8,8); d(8,8); + g.setColor(ij.gui.Roi.getColor()); + m(7,7); d(9,7); + m(7,8); d(9,8); + m(7,9); d(9,9); + } + drawTriangle(16,16); + return; + case WAND: + xOffset = x+2; yOffset = y+1; + dot(4,0); m(2,0); d(3,1); d(4,2); m(0,0); d(1,1); + m(0,2); d(1,3); d(2,4); dot(0,4); m(3,3); d(15,15); + g.setColor(ij.gui.Roi.getColor()); + m(1,2); d(3,2); m(2,1); d(2,3); + return; + case TEXT: + xOffset = x; yOffset = y; + m(1,16); d(9,0); d(16,16); + m(0,16); d(2,16); + m(15,16); d(17,16); + m(4,10); d(13,10); + return; + case MAGNIFIER: + xOffset = x; yOffset = y; + g.drawOval(x+3, y, 13*scale, 13*scale); + m(5,12); d(-1,18); + drawTriangle(15,17); + return; + case HAND: + xOffset = x; yOffset = y; + polyline(5,17,5,16,0,11,0,8,1,8,5,11,5,2,8,2,8,8,8,0,11,0,11,8,11,1,14,1,14,9,14,3,17,3,17,12,16,13,16,17); + return; + case DROPPER: + // draw foreground/background rectangles + g.setColor(backgroundColor); + g.fillRect(x+2*scale, y+3*scale, 15*scale, 16*scale); + g.drawRect(x, y+2*scale, 13*scale, 13*scale); + g.setColor(foregroundColor); + g.fillRect(x, y+2*scale, 13*scale, 13*scale); + // draw dropper icon + xOffset = x+3; yOffset = y-4; + g.setColor(toolColor); + m(12,2); d(14,2); + m(11,3); d(15,3); + m(11,4); d(15,4); + m(8,5); d(15,5); + m(9,6); d(14,6); + polyline(10,7,12,7,12,9); + polyline(9,6,2,13,2,15,4,15,11,8); + g.setColor(gray); + polygon(9,6,2,13,2,15,4,15,11,8); + drawTriangle(12,21); + return; + case ANGLE: + xOffset = x; yOffset = y+3; + m(0,11); d(13,-1); m(0,11); d(16,11); + m(10,11); d(10,8); m(9,7); d(9,6); dot(8,5); + drawDot(13,-2); drawDot(16,10); + return; + } + } + + void drawTriangle(int x, int y) { + g.setColor(triangleColor); + xOffset+=x*scale; yOffset+=y*scale; + m(0,0); d(4,0); m(1,1); d(3,1); m(2,2); d(2,2); + } + + void drawDot(int x, int y) { + g.fillRect(xOffset+x*scale, yOffset+y*scale, 2*scale, 2*scale); + } + + void drawPoint(int x, int y) { + g.setColor(toolColor); + m(x-3,y); d(x+3,y); + m(x,y-3); d(x,y+3); + g.setColor(ij.gui.Roi.getColor()); + dot(x,y); dot(x-1,y-1); + } + + void drawIcon(Graphics g, int tool, int x, int y) { + if (null==g) return; + icon = icons[tool]; + if (icon==null) return; + int x1, y1, x2, y2; + pc = 0; + if (icon.trim().startsWith("icon:")) { + String path = IJ.getDir("macros")+"toolsets/icons/"+icon.substring(icon.indexOf(":")+1); + try { + BufferedImage bi = ImageIO.read(new File(path)); + if (scale==1) + g.drawImage(bi,x-5,y-5,null); + else { + int size = Math.max(bi.getWidth(),bi.getHeight()); + g.drawImage(bi,x-5*scale,y-5*scale, size*scale,size*scale,null); + } + } catch (Exception e) { + IJ.error("Toolbar", "Error reading tool icon:\n"+path); + } + } else { + while (true) { + char command = icon.charAt(pc++); + if (pc>=icon.length()) break; + switch (command) { + case 'B': x+=v(); y+=v(); break; // adjust base + case 'N': x-=v(); y-=v(); break; // adjust base negatively + case 'R': g.drawRect(x+v(), y+v(), v(), v()); break; // rectangle + case 'F': g.fillRect(x+v(), y+v(), v(), v()); break; // filled rectangle + case 'O': g.drawOval(x+v(), y+v(), v(), v()); break; // oval + case 'V': case 'o': g.fillOval(x+v(), y+v(), v(), v()); break; // filled oval + case 'C': // set color + int saveScale = scale; + scale = 1; + int v1=v(), v2=v(), v3=v(); + int red=v1*16, green=v2*16, blue=v3*16; + if (red>255) red=255; if (green>255) green=255; if (blue>255) blue=255; + Color color = v1==1&&v2==2&&v3==3?foregroundColor:new Color(red,green,blue); + g.setColor(color); + scale = saveScale; + break; + case 'L': g.drawLine(x+v(), y+v(), x+v(), y+v()); break; // line + case 'D': g.fillRect(x+v(), y+v(), scale, scale); break; // dot + case 'P': // polyline + Polygon p = new Polygon(); + p.addPoint(x+v(), y+v()); + while (true) { + x2=v(); if (x2==0) break; + y2=v(); if (y2==0) break; + p.addPoint(x+x2, y+y2); + } + g.drawPolyline(p.xpoints, p.ypoints, p.npoints); + break; + case 'G': case 'H':// polygon or filled polygon + p = new Polygon(); + p.addPoint(x+v(), y+v()); + while (true) { + x2=v(); y2=v(); + if (x2==0 && y2==0 && p.npoints>2) + break; + p.addPoint(x+x2, y+y2); + } + if (command=='G') + g.drawPolygon(p.xpoints, p.ypoints, p.npoints); + else + g.fillPolygon(p.xpoints, p.ypoints, p.npoints); + break; + case 'T': // text (one character) + x2 = x+v()-2; + y2 = y+v(); + int size = v()*10+v()+1; + char[] c = new char[1]; + c[0] = pc=icon.length()) break; + } + } + if (menus[tool]!=null && menus[tool].getItemCount()>0) { + xOffset = x; yOffset = y; + drawTriangle(15, 16); + } + } + + int v() { + if (pc>=icon.length()) return 0; + char c = icon.charAt(pc++); + switch (c) { + case '0': return 0*scale; + case '1': return 1*scale; + case '2': return 2*scale; + case '3': return 3*scale; + case '4': return 4*scale; + case '5': return 5*scale; + case '6': return 6*scale; + case '7': return 7*scale; + case '8': return 8*scale; + case '9': return 9*scale; + case 'a': return 10*scale; + case 'b': return 11*scale; + case 'c': return 12*scale; + case 'd': return 13*scale; + case 'e': return 14*scale; + case 'f': return 15*scale; + case 'g': return 16*scale; + case 'h': return 17*scale; + case 'i': return 18*scale; + case 'j': return 19*scale; + case 'k': return 20*scale; + case 'l': return 21*scale; + case 'm': return 22*scale; + case 'n': return 23*scale; + default: return 0; + } + } + + private void showMessage(int tool) { + if (IJ.statusBarProtected()) + return; + if (tool>=UNUSED && tool=UNUSED && current=CUSTOM1) + tool++; + if (IJ.debugMode) IJ.log("Toolbar.setTool: "+tool); + if ((tool==current&&!(tool==RECTANGLE||tool==OVAL||tool==POINT)) || tool<0 || tool>=getNumTools()-1) + return; + if (tool>=CUSTOM1&&tool<=getNumTools()-2) { + if (names[tool]==null) + names[tool] = "Spare tool"; // enable tool + if (names[tool].indexOf("Action Tool")!=-1) + return; + } + if (isLine(tool)) lineType = tool; + setTool2(tool); + } + + private void setTool2(int tool) { + if (!isValidTool(tool)) return; + String previousName = getToolName(); + previousTool = current; + current = tool; + Graphics g = this.getGraphics(); + if (g==null) + return; + down[current] = true; + if (current!=previousTool) + down[previousTool] = false; + Graphics2D g2d = (Graphics2D)g; + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + setStrokeWidth(g2d); + drawButton(g, previousTool); + drawButton(g, current); + if (null==g) return; + g.dispose(); + showMessage(current); + if (IJ.recording()) { + String name = getName(current); + if (name!=null && name.equals("dropper")) disableRecording=true; + if (name!=null && !disableRecording) { + IJ.wait(100); // workaround for OSX/Java 8 bug + Recorder.record("setTool", name); + } + if (name!=null && !name.equals("dropper")) disableRecording=false; + } + if (legacyMode) + repaint(); + if (!previousName.equals(getToolName())) { + IJ.notifyEventListeners(IJEventListener.TOOL_CHANGED);; + repaint(); + } + } + + boolean isValidTool(int tool) { + if (tool<0 || tool>=getNumTools()) + return false; + if (tool>=CUSTOM1 && tool=0) { + int v = (int)value; + if (v>255) v=255; + setForegroundColor(new Color(v,v,v)); + } + foregroundValue = value; + } + + public static double getBackgroundValue() { + return backgroundValue; + } + + /** Sets the background color to grayscale, where value + is between 0 (black) and 255 (white). */ + public static void setBackgroundValue(double value) { + if (value>=0) { + int v = (int)value; + if (v>255) v=255; + setBackgroundColor(new Color(v,v,v)); + } + backgroundValue = value; + } + + private static void setRoiColor(Color c) { + ImagePlus imp = WindowManager.getCurrentImage(); + if (imp==null) return; + ij.gui.Roi roi = imp.getRoi(); + if (roi!=null && (roi.isDrawingTool())) { + roi.setStrokeColor(c); + imp.draw(); + } + } + + /** Returns the size of the selection brush tool, or 0 if the brush tool is not enabled. */ + public static int getBrushSize() { + if (ovalType==BRUSH_ROI) + return brushSize; + else + return 0; + } + + /** Set the size of the selection brush tool, in pixels. */ + public static void setBrushSize(int size) { + brushSize = size; + if (brushSize<1) brushSize = 1; + Prefs.set(BRUSH_SIZE, brushSize); + } + + /** Returns the rounded rectangle arc size, or 0 if the rounded rectangle tool is not enabled. */ + public static int getRoundRectArcSize() { + if (rectType==ROUNDED_RECT_ROI) + return arcSize; + else + return 0; + } + + /** Sets the rounded rectangle corner diameter (pixels). */ + public static void setRoundRectArcSize(int size) { + if (size<=0) + rectType = RECT_ROI; + else { + arcSize = size; + Prefs.set(CORNER_DIAMETER, arcSize); + } + repaintTool(RECTANGLE); + ImagePlus imp = WindowManager.getCurrentImage(); + ij.gui.Roi roi = imp!=null?imp.getRoi():null; + if (roi!=null && roi.getType()== ij.gui.Roi.RECTANGLE) + roi.setCornerDiameter(rectType==ROUNDED_RECT_ROI?arcSize:0); + } + + /** Returns 'true' if the multi-point tool is enabled. */ + public static boolean getMultiPointMode() { + return multiPointMode; + } + + /** Returns the rectangle tool type (RECT_ROI, ROUNDED_RECT_ROI or ROTATED_RECT_ROI). */ + public static int getRectToolType() { + return rectType; + } + + /** Returns the oval tool type (OVAL_ROI, ELLIPSE_ROI or BRUSH_ROI). */ + public static int getOvalToolType() { + return ovalType; + } + + /** Returns the button width (button spacing). */ + public static int getButtonSize() { + return buttonWidth; + } + + public static void repaintTool(int tool) { + Toolbar tb = getInstance(); + if (tb!=null) { + Graphics g = tb.getGraphics(); + if (IJ.debugMode) IJ.log("Toolbar.repaintTool: "+tool+" "+g); + if (g==null) return; + if (dscale>1.0) + tb.setStrokeWidth((Graphics2D)g); + ((Graphics2D)g).setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + tb.drawButton(g, tool); + if (g!=null) g.dispose(); + } + } + + // Returns the toolbar position index of the specified tool + int toolIndex(int tool) { + switch (tool) { + case RECTANGLE: return 0; + case OVAL: return 1; + case POLYGON: return 2; + case FREEROI: return 3; + case LINE: return 4; + case POLYLINE: return 4; + case FREELINE: return 4; + case POINT: return 6; + case WAND: return 7; + case TEXT: return 8; + case MAGNIFIER: return 9; + case HAND: return 10; + case DROPPER: return 11; + case ANGLE: return 5; + case UNUSED: return 12; + default: return tool - 2; + } + } + + // Returns the tool corresponding to the specified x coordinate + private int toolID(int x) { + if (x>buttonWidth*12+gapSize) + x -= gapSize; + int index = x/buttonWidth; + switch (index) { + case 0: return RECTANGLE; + case 1: return OVAL; + case 2: return POLYGON; + case 3: return FREEROI; + case 4: return lineType; + case 5: return ANGLE; + case 6: return POINT; + case 7: return WAND; + case 8: return TEXT; + case 9: return MAGNIFIER; + case 10: return HAND; + case 11: return DROPPER; + default: return index + 3; + } + } + + private boolean inGap(int x) { + return x>=(buttonWidth*12) && x<(buttonWidth*12+gapSize); + } + + public void triggerPopupMenu(int newTool, MouseEvent e, boolean isRightClick, boolean isLongPress) { + mpPrevious = current; + if (isMacroTool(newTool)) { + String name = names[newTool]; + if (newTool==UNUSED || name.contains("Unused Tool")) + return; + if (name.indexOf("Action Tool")!=-1) { + if (e.isPopupTrigger()||e.isMetaDown()) { + name = name.endsWith(" ")?name:name+" "; + tools[newTool].runMacroTool(name+"Options"); + } else { + //drawTool(newTool, true); + //IJ.wait(50); + drawTool(newTool, false); + runMacroTool(newTool); + } + return; + } else { + name = name.endsWith(" ")?name:name+" "; + tools[newTool].runMacroTool(name+"Selected"); + } + } + if (!isLongPress) + setTool2(newTool); + int x = e.getX(); + int y = e.getY(); + if (current==RECTANGLE && isRightClick) { + rectItem.setState(rectType==RECT_ROI); + roundRectItem.setState(rectType==ROUNDED_RECT_ROI); + rotatedRectItem.setState(rectType==ROTATED_RECT_ROI); + if (IJ.isMacOSX()) IJ.wait(10); + rectPopup.show(e.getComponent(),x,y); + mouseDownTime = 0L; + } + if (current==OVAL && isRightClick) { + ovalItem.setState(ovalType==OVAL_ROI); + ellipseItem.setState(ovalType==ELLIPSE_ROI); + brushItem.setState(ovalType==BRUSH_ROI); + if (IJ.isMacOSX()) IJ.wait(10); + ovalPopup.show(e.getComponent(),x,y); + mouseDownTime = 0L; + } + if (current==POINT && isRightClick) { + pointItem.setState(!multiPointMode); + multiPointItem.setState(multiPointMode); + if (IJ.isMacOSX()) IJ.wait(10); + pointPopup.show(e.getComponent(),x,y); + mouseDownTime = 0L; + } + if (isLine(current) && isRightClick) { + straightLineItem.setState(lineType==LINE&&!arrowMode); + polyLineItem.setState(lineType==POLYLINE); + freeLineItem.setState(lineType==FREELINE); + arrowItem.setState(lineType==LINE&&arrowMode); + if (IJ.isMacOSX()) IJ.wait(10); + linePopup.show(e.getComponent(),x,y); + mouseDownTime = 0L; + } + if (current==MAGNIFIER && isRightClick) { + zoomPopup.show(e.getComponent(),x,y); + mouseDownTime = 0L; + } + if (current==DROPPER && isRightClick) { + pickerPopup.show(e.getComponent(),x,y); + mouseDownTime = 0L; + } + if (isMacroTool(current) && isRightClick) { + String name = names[current].endsWith(" ")?names[current]:names[current]+" "; + tools[current].runMacroTool(name+"Options"); + } + if (isPlugInTool(current) && isRightClick) { + tools[current].showPopupMenu(e, this); + } + } + + public void mousePressed(final MouseEvent e) { + boolean disablePopup = false; + int x = e.getX(); + if (inGap(x)) + return; + final int newTool = toolID(x); + if (newTool==getNumTools()-1) { + showSwitchPopupMenu(e); + return; + } + if (!isValidTool(newTool)) + return; + if (menus[newTool]!=null && menus[newTool].getItemCount()>0) { + menus[newTool].show(e.getComponent(), e.getX(), e.getY()); + return; + } + int flags = e.getModifiers(); + boolean isRightClick = e.isPopupTrigger()||(!IJ.isMacintosh()&&(flags&Event.META_MASK)!=0); + boolean doubleClick = newTool==current && (System.currentTimeMillis()-mouseDownTime)<=DOUBLE_CLICK_THRESHOLD; + mouseDownTime = System.currentTimeMillis(); + if (!doubleClick || isRightClick) { + triggerPopupMenu(newTool, e, isRightClick, false); + if (isRightClick) mouseDownTime = 0L; + } else if(!isRightClick) { //double click + if (isMacroTool(current)) { + String name = names[current].endsWith(" ")?names[current]:names[current]+" "; + tools[current].runMacroTool(name+"Options"); + return; + } + if (isPlugInTool(current)) { + tools[current].showOptionsDialog(); + return; + } + ImagePlus imp = WindowManager.getCurrentImage(); + switch (current) { + case RECTANGLE: + if (rectType==ROUNDED_RECT_ROI) + IJ.doCommand("Rounded Rect Tool..."); + else { + disablePopup = true; + IJ.doCommand("Roi Defaults..."); + } + break; + case OVAL: + showBrushDialog(); + break; + case MAGNIFIER: + if (imp!=null) { + ij.gui.ImageCanvas ic = imp.getCanvas(); + if (ic!=null) ic.unzoom(); + } + break; + case LINE: case POLYLINE: case FREELINE: + if (current==LINE && arrowMode) { + IJ.doCommand("Arrow Tool..."); + } else + IJ.runPlugIn("ij.plugin.frame.LineWidthAdjuster", ""); + break; + case ANGLE: + showAngleDialog(); + break; + case POINT: + IJ.doCommand("Point Tool..."); + break; + case WAND: + IJ.doCommand("Wand Tool..."); + break; + case TEXT: + IJ.run("Fonts..."); + break; + case DROPPER: + IJ.doCommand("Color Picker..."); + setTool2(mpPrevious); + break; + default: + } + } + + if (!isRightClick && longClickDelay>0 && !disablePopup) { + if (pressTimer==null) + pressTimer = new Timer(); + pressTimer.schedule(new TimerTask() { + public void run() { + if (pressTimer != null) { + pressTimer.cancel(); + pressTimer = null; + } + triggerPopupMenu(newTool, e, true, true); + } + }, longClickDelay); + } + + } + + public void mouseReleased(MouseEvent e) { + if (pressTimer!=null) { + pressTimer.cancel(); + pressTimer = null; + } + } + + void showSwitchPopupMenu(MouseEvent e) { + String path = IJ.getDir("macros")+"toolsets/"; + if (path==null) + return; + boolean applet = IJ.getApplet()!=null; + File f = new File(path); + String[] list; + if (!applet && f.exists() && f.isDirectory()) { + list = f.list(); + if (list==null) return; + Arrays.sort(list); + } else + list = new String[0]; + switchPopup.removeAll(); + path = IJ.getDir("macros") + "StartupMacros.txt"; + f = new File(path); + if (!f.exists()) { + path = IJ.getDir("macros") + "StartupMacros.ijm"; + f = new File(path); + } + if (!applet && f.exists()) + addItem("Startup Macros"); + else + addItem("StartupMacros*"); + for (int i=0; i=5) + pluginsMenu = menuBar.getMenu(5); + if (pluginsMenu==null || !"Plugins".equals(pluginsMenu.getLabel())) + return; + n = pluginsMenu.getItemCount(); + Menu toolsMenu = null; + for (int i=0; i=CUSTOM1 && tool=CUSTOM1 && toolTools submenu.\n"+ + " \n"+ + "Hold the shift key down while selecting a\n"+ + "toolset to view its source code.\n"+ + " \n"+ + "More macro toolsets are available at\n"+ + " <"+IJ.URL2+"/macros/toolsets/>\n"+ + " \n"+ + "Plugin tools can be downloaded from\n"+ + "the Tools section of the Plugins page at\n"+ + " <"+IJ.URL2+"/plugins/>\n" + ); + return; + } else if (label.endsWith("*")) { + // load from ij.jar + MacroInstaller mi = new MacroInstaller(); + label = label.substring(0, label.length()-1) + ".txt"; + path = "/macros/"+label; + if (IJ.shiftKeyDown()) + showCode(label, mi.openFromIJJar(path)); + else { + resetTools(); + mi.installFromIJJar(path); + } + } else { + // load from ImageJ/macros/toolsets + if (label.equals("Startup Macros")) { + installStartupMacros(); + return; + } else if (label.endsWith(" ")) + path = IJ.getDir("macros")+"toolsets"+File.separator+label.substring(0, label.length()-1)+".ijm"; + else + path = IJ.getDir("macros")+"toolsets"+File.separator+label+".txt"; + try { + if (IJ.shiftKeyDown()) { + IJ.open(path); + IJ.setKeyUp(KeyEvent.VK_SHIFT); + } else + new MacroInstaller().run(path); + } catch(Exception ex) {} + } + } + } + + private void removeTools() { + removeMacroTools(); + setTool(RECTANGLE); + currentSet = "Startup Macros"; + resetPrefs(); + if (nExtraTools>0) { + String name = names[getNumTools()-1]; + String icon = icons[getNumTools()-1]; + nExtraTools = 0; + names[getNumTools()-1] = name; + icons[getNumTools()-1] = icon; + ps = new Dimension(buttonWidth*NUM_BUTTONS-(BUTTON_WIDTH-gapSize)+nExtraTools*BUTTON_WIDTH, buttonHeight); + IJ.getInstance().pack(); + } + } + + private void resetPrefs() { + for (int i=0; i<7; i++) { + String key = TOOL_KEY+(i/10)%10+i%10; + if (!Prefs.get(key, "").equals("")) + Prefs.set(key, ""); + } + } + + public static void restoreTools() { + Toolbar tb = Toolbar.getInstance(); + if (tb!=null) { + if (tb.getToolId()>=UNUSED) + tb.setTool(RECTANGLE); + tb.installStartupMacros(); + } + } + + private void installStartupMacros() { + resetTools(); + String path = IJ.getDir("macros")+"StartupMacros.txt"; + File f = new File(path); + if (!f.exists()) { + path = IJ.getDir("macros")+"StartupMacros.ijm"; + f = new File(path); + } + if (!f.exists()) { + path = IJ.getDir("macros")+"StartupMacros.fiji.ijm"; + f = new File(path); + } + if (!f.exists()) { + IJ.error("StartupMacros not found in\n \n"+IJ.getDir("macros")); + return; + } + if (IJ.shiftKeyDown()) { + IJ.open(path); + IJ.setKeyUp(KeyEvent.VK_SHIFT); + } else { + try { + MacroInstaller mi = new MacroInstaller(); + mi.installFile(path); + } catch + (Exception ex) {} + } + } + + public void actionPerformed(ActionEvent e) { + MenuItem item = (MenuItem)e.getSource(); + String cmd = e.getActionCommand(); + PopupMenu popup = (PopupMenu)item.getParent(); + + if (zoomPopup==popup) { + if ("Zoom In".equals(cmd)) + IJ.runPlugIn("ij.plugin.Zoom", "in"); + else if ("Zoom Out".equals(cmd)) + IJ.runPlugIn("ij.plugin.Zoom", "out"); + else if ("Reset Zoom".equals(cmd)) + IJ.runPlugIn("ij.plugin.Zoom", "orig"); + else if ("View 100%".equals(cmd)) + IJ.runPlugIn("ij.plugin.Zoom", "100%"); + else if ("Zoom To Selection".equals(cmd)) + IJ.runPlugIn("ij.plugin.Zoom", "to"); + else if ("Scale to Fit".equals(cmd)) + IJ.runPlugIn("ij.plugin.Zoom", "scale"); + else if ("Set...".equals(cmd)) + IJ.runPlugIn("ij.plugin.Zoom", "set"); + else if ("Maximize".equals(cmd)) + IJ.runPlugIn("ij.plugin.Zoom", "max"); + disableRecording = true; + setTool(previousTool); + disableRecording = false; + return; + } + + if (pickerPopup==popup) { + if ("White/Black".equals(cmd)) { + setAndRecordForgroundColor(Color.white); + setAndRecordBackgroundColor(Color.black); + } else if ("Black/White".equals(cmd)) { + setAndRecordForgroundColor(Color.black); + setAndRecordBackgroundColor(Color.white); + } else if ("Red".equals(cmd)) + setAndRecordForgroundColor(Color.red); + else if ("Green".equals(cmd)) + setAndRecordForgroundColor(Color.green); + else if ("Blue".equals(cmd)) + setAndRecordForgroundColor(Color.blue); + else if ("Yellow".equals(cmd)) + setAndRecordForgroundColor(Color.yellow); + else if ("Cyan".equals(cmd)) + setAndRecordForgroundColor(Color.cyan); + else if ("Magenta".equals(cmd)) + setAndRecordForgroundColor(Color.magenta); + else if ("Foreground...".equals(cmd)) + setAndRecordForgroundColor(new ij.gui.ColorChooser("Select Foreground Color", foregroundColor, false).getColor()); + else if ("Background...".equals(cmd)) + setAndRecordBackgroundColor(new ij.gui.ColorChooser("Select Background Color", backgroundColor, false).getColor()); + else if ("Colors...".equals(cmd)) { + IJ.run("Colors...", ""); + Recorder.setForegroundColor(getForegroundColor()); + Recorder.setBackgroundColor(getBackgroundColor()); + } else + IJ.run("Color Picker...", ""); + if (!"Color Picker".equals(cmd)) + ColorPicker.update(); + setTool(previousTool); + return; + } + + int tool = -1; + for (int i=CUSTOM1; i=0 && (toolTip.length()-index)>4; + int tool =-1; + for (int i=CUSTOM1; i<=getNumTools()-2; i++) { + if (names[i]==null || toolTip.startsWith(names[i])) { + tool = i; + break; + } + } + if (tool==CUSTOM1) + legacyMode = toolTip.startsWith("Select and Transform Tool"); //TrakEM2 + if (tool==-1 && (nExtraTools0 && toolTip.charAt(index-1)==' ') + names[tool] = toolTip.substring(0, index-1); + else + names[tool] = toolTip.substring(0, index); + } else { + if (toolTip.endsWith("-")) + toolTip = toolTip.substring(0, toolTip.length()-1); + else if (toolTip.endsWith("- ")) + toolTip = toolTip.substring(0, toolTip.length()-2); + names[tool] = toolTip; + } + if (tool==current && (names[tool].indexOf("Action Tool")!=-1||names[tool].indexOf("Unused Tool")!=-1)) + setTool(RECTANGLE); + if (names[tool].endsWith(" Menu Tool")) + installMenu(tool); + if (IJ.debugMode) IJ.log("Toolbar.addTool: "+tool+" "+toolTip); + return tool; + } + + void installMenu(int tool) { + Program pgm = macroInstaller.getProgram(); + Hashtable h = pgm.getMenus(); + if (h==null) return; + String[] commands = (String[])h.get(names[tool]); + if (commands==null) + return; + if (menus[tool]==null) { + menus[tool] = new PopupMenu(""); + ij.gui.GUI.scalePopupMenu(menus[tool]); + add(menus[tool] ); + } else + menus[tool].removeAll(); + for (int i=0; i0 || custom1Name==null) + setPrefs(tool); + } + } + + private void setPrefs(int id) { + if (doNotSavePrefs) + return; + boolean ok = isBuiltInTool(names[id]); + String prefsName = instance.names[id]; + if (!ok) { + String name = names[id]; + int i = name.indexOf(" ("); // remove any hint in parens + if (i>0) { + name = name.substring(0, i); + prefsName=name; + } + } + int index = id - CUSTOM1; + String key = TOOL_KEY + (index/10)%10 + index%10; + Prefs.set(key, prefsName); + } + + private boolean isBuiltInTool(String name) { + for (int i=0; i=CUSTOM1) + instance.setTool(RECTANGLE); + instance.resetTools(); + instance.repaint(); + } + } + + /** Adds a plugin tool to the first available toolbar slot, + or to the last slot if the toolbar is full. */ + public static void addPlugInTool(PlugInTool tool) { + if (instance==null) return; + String nameAndIcon = tool.getToolName()+" - "+tool.getToolIcon(); + instance.addingSingleTool = true; + int id = instance.addTool(nameAndIcon); + instance.addingSingleTool = false; + if (id!=-1) { + instance.tools[id] = tool; + if (instance.menus[id]!=null) + instance.menus[id].removeAll(); + instance.repaintTool(id); + if (!instance.installingStartupTool) + instance.setTool(id); + else + instance.installingStartupTool = false; + instance.setPrefs(id); + } + } + + public static PlugInTool getPlugInTool() { + PlugInTool tool = null; + if (instance==null) + return null; + if (current2; + return rtn; + } + + public static boolean installStartupMacrosTools() { + String customTool0 = Prefs.get(Toolbar.TOOL_KEY+"00", ""); + return customTool0.equals("") || Character.isDigit(customTool0.charAt(0)); + } + + public int getNumTools() { + return NUM_TOOLS + nExtraTools; + } + + /** Sets the tool menu long click delay in milliseconds + * (default is 600). Set to 0 to disable long click triggering. + */ + public static void setLongClickDelay(int delay) { + longClickDelay = delay; + } + + /** Sets the icon of the specified macro or plugin tool.
+ * See: Help>Examples>Tool>Animated Icon Tool; + */ + public static void setIcon(String toolName, String icon) { + if (instance==null) + return; + int tool = 0; + for (int i=CUSTOM1; i0) { + instance.icons[tool] = icon; + Graphics2D g = (Graphics2D)instance.getGraphics(); + instance.setStrokeWidth(g); + instance.drawButton(g, tool); + } + } + +} diff --git a/mrj/26/ij/io/PluginClassLoader.java b/mrj/26/ij/io/PluginClassLoader.java new file mode 100644 index 00000000..b5016ba8 --- /dev/null +++ b/mrj/26/ij/io/PluginClassLoader.java @@ -0,0 +1,313 @@ +package ij.io; + +import ij.IJ; +import java.io.*; +import java.lang.classfile.CodeTransform; +import java.lang.classfile.FieldModel; +import java.lang.classfile.MethodTransform; +import java.lang.classfile.instruction.FieldInstruction; +import java.lang.classfile.instruction.InvokeInstruction; +import java.net.*; +import java.lang.classfile.ClassFile; +import java.lang.classfile.ClassTransform; +import java.lang.constant.ClassDesc; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.CodeSource; +import java.security.cert.Certificate; +import java.util.HashMap; +import java.util.Map; + +/** ImageJ uses this class loader to load plugins and resources from the + * plugins directory and immediate subdirectories. This class loader will + * also load classes and resources from JAR files. + * + *

The class loader searches for classes and resources in the following order: + *

    + *
  1. Plugins directory
  2. + *
  3. Subdirectories of the Plugins directory
  4. + *
  5. JAR and ZIP files in the plugins directory and subdirectories
  6. + *
+ *

The class loader does not recurse into subdirectories beyond the first level. +*/ +public class PluginClassLoader extends URLClassLoader { + protected String path; + private static final Map METADATA_CACHE = new HashMap<>(); + private static final Map APPLET_REMAP = Map.of( + ClassDesc.of("java.applet.Applet"), ClassDesc.of("ij.stub.Applet"), + ClassDesc.of("java.applet.AppletContext"), ClassDesc.of("ij.stub.AppletContext"), + ClassDesc.of("java.applet.AppletStub"), ClassDesc.of("ij.stub.AppletStub"), + ClassDesc.of("java.applet.AudioClip"), ClassDesc.of("ij.stub.AudioClip") + ); + private static final ClassTransform APPLET_TRANSFORMER = createTransform(); + + static { + registerAsParallelCapable(); + } + + /** + * Creates a new PluginClassLoader that searches in the directory path + * passed as a parameter. The constructor automatically finds all JAR and ZIP + * files in the path and first level of subdirectories. The JAR and ZIP files + * are stored in a Vector for future searches. + * @param path the path to the plugins directory. + */ + public PluginClassLoader(String path) { + super(new URL[0], IJ.class.getClassLoader()); + init(path); + } + + /** This version of the constructor is used when ImageJ is launched using Java WebStart. */ + public PluginClassLoader(String path, boolean callSuper) { + super(new URL[0], Thread.currentThread().getContextClassLoader()); + init(path); + } + + void init(String path) { + this.path = path; + + //find all JAR files on the path and subdirectories + File f = new File(path); + try { + // Add plugin directory to search path + addURL(f.toURI().toURL()); + } catch (MalformedURLException e) { + ij.IJ.log("PluginClassLoader: "+e); + } + String[] list = f.list(); + if (list==null) + return; + for (int i=0; i findClass(String name) throws ClassNotFoundException { + // Exclude java classes from remapping + if (name.startsWith("java.") || name.startsWith("javax.") || name.startsWith("sun.")) { + return super.findClass(name); + } + + var resourceName = name.replace('.', '/').concat(".class"); + + var resourceUrl = findResource(resourceName); + if (resourceUrl == null) { + IO.println("Resource not found: " + resourceName); + throw new ClassNotFoundException(name); + } + + try { + var conn = resourceUrl.openConnection(); + + try (InputStream in = conn.getInputStream()) { + var bytes = in.readAllBytes(); + var remapped = maybeRemapAppletRefs(bytes); + + var lastDot = name.lastIndexOf('.'); + if (lastDot != -1) { + var pkg = name.substring(0, lastDot); + if (getDefinedPackage(pkg) == null) { + definePackage(pkg, null, null, null, null, null, null, null); + } + } + + try { + var basePath = getCodeSourcePath(resourceUrl, resourceName); + + CodeSource codeSource = null; + if (conn instanceof JarURLConnection jarURLConnection) { + var jarEntry = jarURLConnection.getJarEntry(); + if (jarEntry != null) { + codeSource = new CodeSource(resourceUrl, jarEntry.getCodeSigners()); + } + } + + if (codeSource == null) { + codeSource = getCodeSource(basePath, conn); + } + + //Files.write(Paths.get(name + ".class"), remapped); + + return defineClass(name, remapped, 0, remapped.length, codeSource); + } catch (IOException | IllegalStateException e) { + throw new ClassNotFoundException(name, e); + } + } catch (IOException e) { + throw new ClassNotFoundException(name, e); + } + } catch (IOException e) { + throw new ClassNotFoundException(name, e); + } + } + + private static byte[] maybeRemapAppletRefs(byte[] classBytes) { + var cf = ClassFile.of(); + return cf.transformClass(cf.parse(classBytes), APPLET_TRANSFORMER); + } + + private static ClassTransform createTransform() { + CodeTransform codeTransform = (codeBuilder, e) -> { + switch (e) { + case InvokeInstruction i -> { + var owner = i.owner().asSymbol(); + var type = i.typeSymbol(); + var rOwner = APPLET_REMAP.get(i.owner().asSymbol()); + var rType = APPLET_REMAP.get(type.returnType()); + + if (rOwner != null || rType != null) { + if (rType != null) { + type = type.changeReturnType(rType); + } + if (rOwner != null) { + owner = rOwner; + } + codeBuilder.invoke(i.opcode(), owner, i.name().stringValue(), type, i.isInterface()); + } else { + codeBuilder.accept(i); + } + } + case FieldInstruction f -> { + var type = APPLET_REMAP.get(f.typeSymbol()); + if (type != null) { + codeBuilder.fieldAccess(f.opcode(), f.owner().asSymbol(), f.name().stringValue(), type); + } else { + codeBuilder.accept(f); + } + } + default -> codeBuilder.accept(e); + } + }; + + var methodTransform = MethodTransform.transformingCode(codeTransform); + + ClassTransform fieldRemapper = (classBuilder, e) -> { + switch (e) { + case FieldModel f -> { + var rf = APPLET_REMAP.get(f.fieldTypeSymbol()); + if (rf != null) { + classBuilder.withField(f.fieldName().stringValue(), rf, f.flags().flagsMask()); + } else { + classBuilder.accept(f); + } + } + default -> classBuilder.accept(e); + } + }; + + return fieldRemapper.andThen(ClassTransform.transformingMethods(methodTransform)); + } + + private CodeSource getCodeSource(URI base, URLConnection conn) { + try { + return METADATA_CACHE.computeIfAbsent(base, (URI uri) -> { + try { + if (conn instanceof JarURLConnection jarURLConnection) { + return new CodeSource(jarURLConnection.getJarFileURL(), jarURLConnection.getCertificates()); + } + + var path = Path.of(uri); + + if (Files.isDirectory(path)) { + return new CodeSource(path.toUri().toURL(), (Certificate[]) null); + } + } catch (IOException e) { + if (IJ.debugMode) { + e.printStackTrace(); + } + } + + return null; + }); + } catch (IllegalStateException e) { + if (IJ.debugMode) { + e.printStackTrace(); + } + + return null; + } + } + + private URI getCodeSourcePath(URL url, String className) throws IllegalStateException { + try { + if ("jar".equals(url.getProtocol())) { + var file = url.getFile(); + var sep = file.indexOf("!/"); + + if (sep == -1) { + throw new IllegalStateException("Invalid jar url: " + url); + } + + return new URI("jar:" + file.substring(0, sep) + "!/"); + } else { + var path = Path.of(url.toURI()); + var classPath = Path.of(className); + + if (!path.endsWith(classPath)) { + throw new IllegalStateException("URL does not end with resource: " + className); + } + + var base = path; + for (int i = 0; i < classPath.getNameCount(); i++) { + base = base.getParent(); + if (base == null) break; + } + + if (base == null) { + throw new IllegalStateException("Cannot determine code source for " + url); + } + + return base.toUri(); + } + } catch (Exception e) { + throw new IllegalStateException("Failed to get CodeSource path", e); + } + } +} diff --git a/mrj/26/ij/macro/Functions.java b/mrj/26/ij/macro/Functions.java new file mode 100644 index 00000000..62b5456f --- /dev/null +++ b/mrj/26/ij/macro/Functions.java @@ -0,0 +1,8599 @@ +package ij.macro; + +import java.awt.Color; +import java.awt.Dialog; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.Frame; +import java.awt.GraphicsEnvironment; +import java.awt.Point; +import java.awt.Polygon; +import java.awt.Rectangle; +import java.awt.Shape; +import java.awt.TextArea; +import java.awt.Toolkit; +import java.awt.Window; +import java.awt.datatransfer.Clipboard; +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.StringSelection; +import java.awt.datatransfer.Transferable; +import java.awt.event.KeyEvent; +import java.awt.geom.Ellipse2D; +import java.awt.geom.GeneralPath; +import java.awt.geom.Rectangle2D; +import java.awt.image.IndexColorModel; +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.CharArrayWriter; +import java.io.DataInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.PrintWriter; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Date; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Locale; +import java.util.Properties; +import java.util.Random; +import java.util.Vector; + +import ij.CompositeImage; +import ij.IJ; +import ij.ImageJ; +import ij.ImagePlus; +import ij.ImageStack; +import ij.Macro; +import ij.Menus; +import ij.Prefs; +import ij.Undo; +import ij.WindowManager; +import ij.gui.Arrow; +import ij.gui.EllipseRoi; +import ij.gui.GenericDialog; +import ij.gui.HistogramWindow; +import ij.gui.ImageCanvas; +import ij.gui.ImageWindow; +import ij.gui.Line; +import ij.gui.NonBlockingGenericDialog; +import ij.gui.Overlay; +import ij.gui.Plot; +import ij.gui.PlotWindow; +import ij.gui.PointRoi; +import ij.gui.PolygonRoi; +import ij.gui.ProfilePlot; +import ij.gui.Roi; +import ij.gui.RotatedRectRoi; +import ij.gui.ShapeRoi; +import ij.gui.StackWindow; +import ij.gui.TextRoi; +import ij.gui.Toolbar; +import ij.gui.WaitForUserDialog; +import ij.gui.Wand; +import ij.gui.YesNoCancelDialog; +import ij.io.FileInfo; +import ij.io.FileSaver; +import ij.io.OpenDialog; +import ij.io.Opener; +import ij.io.SaveDialog; +import ij.measure.Calibration; +import ij.measure.CurveFitter; +import ij.measure.Measurements; +import ij.measure.ResultsTable; +import ij.plugin.BatchProcessor; +import ij.plugin.Colors; +import ij.plugin.FITS_Reader; +import ij.plugin.FolderOpener; +import ij.plugin.ImageCalculator; +import ij.plugin.ImageInfo; +import ij.plugin.MacroInstaller; +import ij.plugin.Macro_Runner; +import ij.plugin.Orthogonal_Views; +import ij.plugin.Straightener; +import ij.plugin.filter.*; +import ij.plugin.frame.*; +import ij.process.AutoThresholder; +import ij.process.ColorProcessor; +import ij.process.FHT; +import ij.process.FloatBlitter; +import ij.process.FloatPolygon; +import ij.process.FloatProcessor; +import ij.process.FloodFiller; +import ij.process.ImageConverter; +import ij.process.ImageProcessor; +import ij.process.ImageStatistics; +import ij.process.LUT; +import ij.process.ShortProcessor; +import ij.process.StackStatistics; +import ij.text.*; +import ij.util.IJMath; +import ij.util.Tools; +import ij.util.WildcardMatch; + + +/** This class implements the built-in macro functions. */ +public class Functions implements ij.macro.MacroConstants, Measurements { + ij.macro.Interpreter interp; + ij.macro.Program pgm; + boolean updateNeeded; + boolean autoUpdate = true; + ImageProcessor defaultIP; + ImagePlus defaultImp; + int imageType; + boolean fontSet; + Color globalColor; + double globalValue = Double.NaN; + int globalLineWidth; + Plot plot; + static int plotID; + int justification = ImageProcessor.LEFT_JUSTIFY; + Font font; + GenericDialog gd; + PrintWriter writer; + boolean altKeyDown, shiftKeyDown; + boolean antialiasedText = IJ.isMacOSX()?true:false; //non-antialiased text is broken on macOS + boolean nonScalableText; + StringBuffer buffer; + RoiManager roiManager; + Properties props; + CurveFitter fitter; + boolean showFitDialog; + boolean logFitResults; + boolean resultsPending; + Overlay offscreenOverlay; + Overlay overlayClipboard; + static Roi roiClipboard; + GeneralPath overlayPath; + boolean overlayDrawLabels; + ResultsTable currentTable; + ResultsTable unUpdatedTable; + + // save/restore settings + boolean saveSettingsCalled; + boolean usePointerCursor, hideProcessStackDialog; + float divideByZeroValue; + int jpegQuality; + int saveLineWidth; + boolean doScaling; + boolean weightedColor; + double[] weights; + boolean interpolateScaledImages, open100Percent, blackCanvas; + boolean useJFileChooser,debugMode; + Color foregroundColor, backgroundColor, roiColor; + boolean pointAutoMeasure, requireControlKey, useInvertingLut; + boolean disablePopup; + int measurements; + int decimalPlaces; + boolean blackBackground; + boolean autoContrast; + static WaitForUserDialog waitForUserDialog; + int pasteMode; + boolean expandableArrays = true; + int plotWidth; + int plotHeight; + int plotFontSize; + boolean plotInterpolate; + boolean plotNoGridLines; + boolean plotNoTicks; + boolean profileVerticalProfile; + boolean profileSubPixelResolution; + boolean waitForCompletion = true; + + + Functions(ij.macro.Interpreter interp, ij.macro.Program pgm) { + this.interp = interp; + this.pgm = pgm; + } + + void doFunction(int type) { + switch (type) { + case RUN: doRun(); break; + case SELECT: selectWindow(); break; + case WAIT: IJ.wait((int)getArg()); break; + case BEEP: interp.getParens(); IJ.beep(); break; + case RESET_MIN_MAX: interp.getParens(); IJ.resetMinAndMax(); resetImage(); break; + case RESET_THRESHOLD: interp.getParens(); IJ.resetThreshold(); resetImage(); break; + case PRINT: case WRITE: print(); break; + case DO_WAND: doWand(); break; + case SET_MIN_MAX: setMinAndMax(); break; + case SET_THRESHOLD: setThreshold(); break; + case SET_TOOL: setTool(); break; + case SET_FOREGROUND: setForegroundColor(); break; + case SET_BACKGROUND: setBackgroundColor(); break; + case SET_COLOR: setColor(); break; + case MAKE_LINE: makeLine(); break; + case MAKE_ARROW: makeArrow(); break; + case MAKE_OVAL: makeOval(); break; + case MAKE_RECTANGLE: makeRectangle(); break; + case MAKE_ROTATED_RECT: makeRotatedRectangle(); break; + case DUMP: interp.dump(); break; + case LINE_TO: lineTo(); break; + case MOVE_TO: moveTo(); break; + case DRAW_LINE: drawLine(); break; + case REQUIRES: requires(); break; + case AUTO_UPDATE: autoUpdate = getBooleanArg(); break; + case UPDATE_DISPLAY: interp.getParens(); updateNeeded=true; updateDisplay(); break; + case DRAW_STRING: drawString(); break; + case SET_PASTE_MODE: IJ.setPasteMode(getStringArg()); break; + case DO_COMMAND: doCommand(); break; + case SHOW_STATUS: showStatus(); break; + case SHOW_PROGRESS: showProgress(); break; + case SHOW_MESSAGE: showMessage(false); break; + case SHOW_MESSAGE_WITH_CANCEL: showMessage(true); break; + case SET_PIXEL: case PUT_PIXEL: setPixel(); break; + case SNAPSHOT: case RESET: case FILL: doIPMethod(type); break; + case SET_LINE_WIDTH: setLineWidth((int)getArg()); break; + case CHANGE_VALUES: changeValues(); break; + case SELECT_IMAGE: selectImage(); break; + case EXIT: exit(); break; + case SET_LOCATION: setLocation(); break; + case GET_CURSOR_LOC: getCursorLoc(); break; + case GET_LINE: getLine(); break; + case GET_VOXEL_SIZE: getVoxelSize(); break; + case GET_HISTOGRAM: getHistogram(); break; + case GET_BOUNDING_RECT: case GET_BOUNDS: getBounds(true); break; + case GET_LUT: getLut(); break; + case SET_LUT: setLut(); break; + case GET_COORDINATES: getCoordinates(); break; + case MAKE_SELECTION: makeSelection(); break; + case SET_RESULT: setResult(null); break; + case UPDATE_RESULTS: updateResults(); break; + case SET_BATCH_MODE: setBatchMode(); break; + case SET_JUSTIFICATION: setJustification(); break; + case SET_Z_COORDINATE: setZCoordinate(); break; + case GET_THRESHOLD: getThreshold(); break; + case GET_PIXEL_SIZE: getPixelSize(); break; + case SETUP_UNDO: interp.getParens(); Undo.setup(Undo.MACRO, getImage()); break; + case SAVE_SETTINGS: saveSettings(); break; + case RESTORE_SETTINGS: restoreSettings(); break; + case SET_KEY_DOWN: setKeyDown(); break; + case OPEN: open(); break; + case SET_FONT: setFont(); break; + case GET_MIN_AND_MAX: getMinAndMax(); break; + case CLOSE: close(); break; + case SET_SLICE: setSlice(); break; + case NEW_IMAGE: newImage(); break; + case SAVE: IJ.save(getStringArg()); break; + case SAVE_AS: saveAs(); break; + case SET_AUTO_THRESHOLD: setAutoThreshold(); break; + case RENAME: getImage().setTitle(getStringArg()); break; + case GET_STATISTICS: getStatistics(true); break; + case GET_RAW_STATISTICS: getStatistics(false); break; + case FLOOD_FILL: floodFill(); break; + case RESTORE_PREVIOUS_TOOL: restorePreviousTool(); break; + case SET_VOXEL_SIZE: setVoxelSize(); break; + case GET_LOCATION_AND_SIZE: getLocationAndSize(); break; + case GET_DATE_AND_TIME: getDateAndTime(); break; + case SET_METADATA: setMetadata(); break; + case CALCULATOR: imageCalculator(); break; + case SET_RGB_WEIGHTS: setRGBWeights(); break; + case MAKE_POLYGON: makePolygon(); break; + case SET_SELECTION_NAME: setSelectionName(); break; + case DRAW_RECT: case FILL_RECT: case DRAW_OVAL: case FILL_OVAL: drawOrFill(type); break; + case SET_OPTION: setOption(); break; + case SHOW_TEXT: showText(); break; + case SET_SELECTION_LOC: setSelectionLocation(); break; + case GET_DIMENSIONS: getDimensions(); break; + case WAIT_FOR_USER: waitForUser(); break; + case MAKE_POINT: makePoint(); break; + case MAKE_TEXT: makeText(); break; + case MAKE_ELLIPSE: makeEllipse(); break; + case GET_DISPLAYED_AREA: getDisplayedArea(); break; + case TO_SCALED: toScaled(); break; + case TO_UNSCALED: toUnscaled(); break; + } + } + + final double getFunctionValue(int type) { + double value = 0.0; + switch (type) { + case GET_PIXEL: value = getPixel(); break; + case ABS: case COS: case EXP: case FLOOR: case LOG: case ROUND: + case SIN: case SQRT: case TAN: case ATAN: case ASIN: case ACOS: + value = math(type); + break; + case MATH: value = doMath(); break; + case MAX_OF: case MIN_OF: case POW: case ATAN2: value=math2(type); break; + case GET_TIME: interp.getParens(); value=System.currentTimeMillis(); break; + case GET_WIDTH: interp.getParens(); value=getImage().getWidth(); break; + case GET_HEIGHT: interp.getParens(); value=getImage().getHeight(); break; + case RANDOM: value=random(); break; + case GET_COUNT: case NRESULTS: value=getResultsCount(); break; + case GET_RESULT: value=getResult(null); break; + case GET_NUMBER: value=getNumber(); break; + case NIMAGES: value=getImageCount(); break; + case NSLICES: value=getStackSize(); break; + case LENGTH_OF: value=lengthOf(); break; + case GET_ID: interp.getParens(); value=getImage().getID(); break; + case BIT_DEPTH: interp.getParens(); value = getImage().getBitDepth(); break; + case SELECTION_TYPE: value=getSelectionType(); break; + case IS_OPEN: value=isOpen(); break; + case IS_ACTIVE: value=isActive(); break; + case INDEX_OF: value=indexOf(null); break; + case LAST_INDEX_OF: value=getFirstString().lastIndexOf(getLastString()); break; + case CHAR_CODE_AT: value=charCodeAt(); break; + case GET_BOOLEAN: value=getBoolean(); break; + case STARTS_WITH: case ENDS_WITH: value = startsWithEndsWith(type); break; + case IS_NAN: value = Double.isNaN(getArg())?1:0; break; + case GET_ZOOM: value = getZoom(); break; + case PARSE_FLOAT: value = parseDouble(getStringArg()); break; + case PARSE_INT: value = parseInt(); break; + case IS_KEY_DOWN: value = isKeyDown(); break; + case GET_SLICE_NUMBER: interp.getParens(); value=getImage().getCurrentSlice(); break; + case SCREEN_WIDTH: case SCREEN_HEIGHT: value = getScreenDimension(type); break; + case CALIBRATE: value = getImage().getCalibration().getCValue(getArg()); break; + case ROI_MANAGER: value = roiManager(); break; + case TOOL_ID: interp.getParens(); value = Toolbar.getToolId(); break; + case IS: value = is(); break; + case GET_VALUE: value = getValue(); break; + case STACK: value = doStack(); break; + case MATCHES: value = matches(null); break; + case GET_STRING_WIDTH: value = getStringWidth(); break; + case FIT: value = fit(); break; + case OVERLAY: value = doOverlay(); break; + case SELECTION_CONTAINS: value = selectionContains(); break; + case PLOT: value = doPlot(); break; + default: + interp.error("Numeric function expected"); + } + return value; + } + + String getStringFunction(int type) { + String str; + switch (type) { + case D2S: str = d2s(); break; + case TO_HEX: str = toString(16); break; + case TO_BINARY: str = toString(2); break; + case GET_TITLE: interp.getParens(); str=getImage().getTitle(); break; + case GET_STRING: str = getStringDialog(); break; + case SUBSTRING: str=substring(null); break; + case FROM_CHAR_CODE: str = fromCharCode(); break; + case GET_INFO: str = getInfo(); break; + case GET_IMAGE_INFO: interp.getParens(); str = getImageInfo(); break; + case GET_DIRECTORY: case GET_DIR: str = getDirectory(); break; + case GET_ARGUMENT: interp.getParens(); str=interp.argument!=null?interp.argument:""; break; + case TO_LOWER_CASE: str = getStringArg().toLowerCase(Locale.US); break; + case TO_UPPER_CASE: str = getStringArg().toUpperCase(Locale.US); break; + case RUN_MACRO: str = runMacro(false); break; + case EVAL: str = runMacro(true); break; + case TO_STRING: str = doToString(); break; + case REPLACE: str = replace(null); break; + case DIALOG: str = doDialog(); break; + case GET_METADATA: str = getMetadata(); break; + case FILE: str = doFile(); break; + case SELECTION_NAME: str = selectionName(); break; + case GET_VERSION: interp.getParens(); str = IJ.getVersion(); break; + case GET_RESULT_LABEL: str = getResultLabel(); break; + case CALL: str = call(); break; + case STRING: str = doString(); break; + case EXT: str = doExt(); break; + case EXEC: str = exec(); break; + case LIST: str = doList(); break; + case DEBUG: str = debug(); break; + case IJ_CALL: str = doIJ(); break; + case GET_RESULT_STRING: str = getResultString(null); break; + case TRIM: str = trim(); break; + default: + str=""; + interp.error("String function expected"); + } + return str; + } + + ij.macro.Variable[] getArrayFunction(int type) { + ij.macro.Variable[] array; + switch (type) { + case GET_PROFILE: array=getProfile(); break; + case NEW_ARRAY: array = newArray(); break; + case SPLIT: array = split(); break; + case GET_FILE_LIST: array = getFileList(); break; + case GET_FONT_LIST: array = getFontList(); break; + case NEW_MENU: array = newMenu(); break; + case GET_LIST: array = getList(); break; + case ARRAY_FUNC: array = doArray(); break; + default: + array = null; + interp.error("Array function expected"); + } + return array; + } + + // Functions returning a string must be added + // to isStringFunction(String,int). + ij.macro.Variable getVariableFunction(int type) { + ij.macro.Variable var = null; + switch (type) { + case TABLE: var = doTable(); break; + case ROI: var = doRoi(); break; + case ROI_MANAGER2: var = doRoiManager(); break; + case PROPERTY: var = doProperty(); break; + case IMAGE: var = doImage(); break; + case COLOR: var = doColor(); break; + default: + interp.error("Variable function expected"); + } + if (var==null) + var = new ij.macro.Variable(Double.NaN); + return var; + } + + private void setLineWidth(int width) { + if (WindowManager.getCurrentImage()!=null) { + if (overlayPath!=null && width!=globalLineWidth) + addDrawingToOverlay(getImage()); + getProcessor().setLineWidth(width); + } + globalLineWidth = width; + } + + private double doMath() { + interp.getToken(); + if (interp.token!='.') + interp.error("'.' expected"); + interp.getToken(); + if (!(interp.token==WORD||interp.token==NUMERIC_FUNCTION)) + interp.error("Function name expected: "); + String name = interp.tokenString; + if (name.equals("min")) + return Math.min(getFirstArg(), getLastArg()); + else if (name.equals("max")) + return Math.max(getFirstArg(), getLastArg()); + else if (name.equals("pow")) + return Math.pow(getFirstArg(), getLastArg()); + else if (name.equals("atan2")) + return Math.atan2(getFirstArg(), getLastArg()); + else if (name.equals("constrain")) + return Math.min(Math.max(getFirstArg(), getNextArg()), getLastArg()); + else if (name.equals("map")) { + double value = getFirstArg(); + double fromLow = getNextArg(); + double fromHigh = getNextArg(); + double toLow = getNextArg(); + double toHigh = getLastArg(); + return (value-fromLow)*(toHigh-toLow)/(fromHigh-fromLow)+toLow; + } + double arg = getArg(); + if (name.equals("ceil")) + return Math.ceil(arg); + else if (name.equals("abs")) + return Math.abs(arg); + else if (name.equals("cos")) + return Math.cos(arg); + else if (name.equals("exp")) + return Math.exp(arg); + else if (name.equals("floor")) + return Math.floor(arg); + else if (name.equals("log")) + return Math.log(arg); + else if (name.equals("log10")) + return Math.log10(arg); + else if (name.equals("round")) + return Math.round(arg); + else if (name.equals("sin")) + return Math.sin(arg); + else if (name.equals("sqr")) + return arg*arg; + else if (name.equals("sqrt")) + return Math.sqrt(arg); + else if (name.equals("tan")) + return Math.tan(arg); + else if (name.equals("atan")) + return Math.atan(arg); + else if (name.equals("asin")) + return Math.asin(arg); + else if (name.equals("acos")) + return Math.acos(arg); + else if (name.equals("erf")) + return IJMath.erf(arg); + else if (name.equals("toRadians")) + return Math.toRadians(arg); + else if (name.equals("toDegrees")) + return Math.toDegrees(arg); + else + interp.error("Unrecognized function name"); + return Double.NaN; + } + + final double math(int type) { + double arg = getArg(); + switch (type) { + case ABS: return Math.abs(arg); + case COS: return Math.cos(arg); + case EXP: return Math.exp(arg); + case FLOOR: return Math.floor(arg); + case LOG: return Math.log(arg); + case ROUND: return Math.floor(arg + 0.5); + case SIN: return Math.sin(arg); + case SQRT: return Math.sqrt(arg); + case TAN: return Math.tan(arg); + case ATAN: return Math.atan(arg); + case ASIN: return Math.asin(arg); + case ACOS: return Math.acos(arg); + default: return 0.0; + } + } + + final double math2(int type) { + double a1 = getFirstArg(); + double a2 = getLastArg(); + switch (type) { + case MIN_OF: return Math.min(a1, a2); + case MAX_OF: return Math.max(a1, a2); + case POW: return Math.pow(a1, a2); + case ATAN2: return Math.atan2(a1, a2); + default: return 0.0; + } + } + + final String getString() { + String str = interp.getStringTerm(); + while (true) { + interp.getToken(); + if (interp.token=='+') + str += interp.getStringTerm(); + else { + interp.putTokenBack(); + break; + } + }; + return str; + } + + final boolean isStringFunction() { + ij.macro.Symbol symbol = pgm.table[interp.tokenAddress]; + return symbol.type==D2S; + } + + final double getArg() { + interp.getLeftParen(); + double arg = interp.getExpression(); + interp.getRightParen(); + return arg; + } + + final double getFirstArg() { + interp.getLeftParen(); + return interp.getExpression(); + } + + final double getNextArg() { + interp.getComma(); + return interp.getExpression(); + } + + final double getLastArg() { + interp.getComma(); + double arg = interp.getExpression(); + interp.getRightParen(); + return arg; + } + + String getStringArg() { + interp.getLeftParen(); + String arg = getString(); + interp.getRightParen(); + return arg; + } + + final String getFirstString() { + interp.getLeftParen(); + return getString(); + } + + final String getNextString() { + interp.getComma(); + return getString(); + } + + final String getLastString() { + interp.getComma(); + String arg = getString(); + interp.getRightParen(); + return arg; + } + + boolean getBooleanArg() { + interp.getLeftParen(); + double arg = interp.getBooleanExpression(); + interp.checkBoolean(arg); + interp.getRightParen(); + return arg==0?false:true; + } + + final ij.macro.Variable getVariableArg() { + interp.getLeftParen(); + ij.macro.Variable v = getVariable(); + interp.getRightParen(); + return v; + } + + final ij.macro.Variable getFirstVariable() { + interp.getLeftParen(); + return getVariable(); + } + + final ij.macro.Variable getNextVariable() { + interp.getComma(); + return getVariable(); + } + + final ij.macro.Variable getLastVariable() { + interp.getComma(); + ij.macro.Variable v = getVariable(); + interp.getRightParen(); + return v; + } + + final ij.macro.Variable getVariable() { + interp.getToken(); + if (interp.token!=WORD) + interp.error("Variable expected"); + ij.macro.Variable v = interp.lookupLocalVariable(interp.tokenAddress); + if (v==null) + v = interp.push(interp.tokenAddress, 0.0, null, interp); + ij.macro.Variable[] array = v.getArray(); + if (array!=null) { + int index = interp.getIndex(); + checkIndex(index, 0, v.getArraySize()-1); + v = array[index]; + } + return v; + } + + final ij.macro.Variable getFirstArrayVariable() { + interp.getLeftParen(); + return getArrayVariable(); + } + + final ij.macro.Variable getNextArrayVariable() { + interp.getComma(); + return getArrayVariable(); + } + + final ij.macro.Variable getLastArrayVariable() { + interp.getComma(); + ij.macro.Variable v = getArrayVariable(); + interp.getRightParen(); + return v; + } + + final ij.macro.Variable getArrayVariable() { + interp.getToken(); + if (interp.token!=WORD) + interp.error("Variable expected"); + ij.macro.Variable v = interp.lookupLocalVariable(interp.tokenAddress); + if (v==null) + v = interp.push(interp.tokenAddress, 0.0, null, interp); + return v; + } + + final double[] getFirstArray() { + interp.getLeftParen(); + return getNumericArray(); + } + + final double[] getNextArray() { + interp.getComma(); + return getNumericArray(); + } + + final double[] getLastArray() { + interp.getComma(); + double[] a = getNumericArray(); + interp.getRightParen(); + return a; + } + + double[] getNumericArray() { + ij.macro.Variable[] a1 = getArray(); + double[] a2 = new double[a1.length]; + for (int i=0; iupper) + interp.error("Index ("+index+") is outside of the "+lower+"-"+upper+" range"); + } + + void doRun() { + interp.getLeftParen(); + String arg1 = getString(); + interp.getToken(); + if (!(interp.token==')' || interp.token==',')) + interp.error("',' or ')' expected"); + String arg2 = null; + if (interp.token==',') { + arg2 = getString(); + interp.getRightParen(); + } + IJ.run(this.interp, arg1, arg2); + resetImage(); + IJ.setKeyUp(IJ.ALL_KEYS); + shiftKeyDown = altKeyDown = false; + } + + private void selectWindow() { + String title = getStringArg(); + if (resultsPending && "Results".equals(title)) { + ResultsTable rt = ResultsTable.getResultsTable(); + if (rt!=null && rt.size()>0) + rt.show("Results"); + } + IJ.selectWindow(title); + resetImage(); + } + + void setForegroundColor() { + boolean isImage = WindowManager.getCurrentImage()!=null; + int lnWidth = 0; + if (isImage) + lnWidth = getProcessor().getLineWidth(); + int red=0, green=0, blue=0; + int arg1 = (int)getFirstArg(); + if (interp.nextToken()==')') { + interp.getRightParen(); + red = (arg1&0xff0000)>>16; + green = (arg1&0xff00)>>8; + blue = arg1&0xff; + } else { + red = arg1; + green = (int)getNextArg(); + blue = (int)getLastArg(); + } + IJ.setForegroundColor(red, green, blue); + resetImage(); + if (isImage) + setLineWidth(lnWidth); + globalColor = null; + globalValue = Double.NaN; + } + + void setBackgroundColor() { + int red=0, green=0, blue=0; + int arg1 = (int)getFirstArg(); + if (interp.nextToken()==')') { + interp.getRightParen(); + red = (arg1&0xff0000)>>16; + green = (arg1&0xff00)>>8; + blue = arg1&0xff; + } else { + red = arg1; + green = (int)getNextArg(); + blue = (int)getLastArg(); + } + IJ.setBackgroundColor(red, green, blue); + resetImage(); + } + + void setColor() { + interp.getLeftParen(); + if (isStringArg()) { + globalColor = getColor(); + globalValue = Double.NaN; + ImagePlus imp = WindowManager.getCurrentImage(); + if (imp!=null) { + if (overlayPath!=null) + addDrawingToOverlay(imp); + getProcessor().setColor(globalColor); + getProcessor().fillColorSet(true); + } + interp.getRightParen(); + return; + } + double arg1 = interp.getExpression(); + if (interp.nextToken()==')') { + interp.getRightParen(); + setColor(arg1); + return; + } + int red=(int)arg1, green=(int)getNextArg(), blue=(int)getLastArg(); + if (red<0) red=0; if (green<0) green=0; if (blue<0) blue=0; + if (red>255) red=255; if (green>255) green=255; if (blue>255) blue=255; + globalColor = new Color(red, green, blue); + globalValue = Double.NaN; + if (WindowManager.getCurrentImage()!=null) { + getProcessor().setColor(globalColor); + getProcessor().fillColorSet(true); + } + } + + void setColor(double value) { + ImageProcessor ip = getProcessor(); + ImagePlus imp = getImage(); + switch (imp.getBitDepth()) { + case 8: + if (value<0 || value>255) + interp.error("Argument out of 8-bit range (0-255)"); + ip.setValue(value); + break; + case 16: + if (imp.getLocalCalibration().isSigned16Bit()) + value += 32768; + if (value<0 || value>65535) + interp.error("Argument out of 16-bit range (0-65535)"); + ip.setValue(value); + break; + default: + ip.setValue(value); + break; + } + globalValue = value; + globalColor = null; + ip.fillColorSet(true); + } + + void makeLine() { + double x1d = getFirstArg(); + double y1d = getNextArg(); + double x2d = getNextArg(); + interp.getComma(); + double y2d = interp.getExpression(); + interp.getToken(); + if (interp.token==')') + IJ.makeLine(x1d, y1d, x2d, y2d); + else { + Polygon points = new Polygon(); + points.addPoint((int)Math.round(x1d),(int)Math.round(y1d)); + points.addPoint((int)Math.round(x2d),(int)Math.round(y2d)); + while (interp.token==',') { + int x = (int)Math.round(interp.getExpression()); + if (points.npoints==2 && interp.nextToken()==')') { + interp.getRightParen(); + Roi line = new Line(x1d, y1d, x2d, y2d); + line.updateWideLine((float)x); + getImage().setRoi(line); + return; + } + interp.getComma(); + int y = (int)Math.round(interp.getExpression()); + points.addPoint(x,y); + interp.getToken(); + } + getImage().setRoi(new PolygonRoi(points, Roi.POLYLINE)); + } + resetImage(); + } + + void makeArrow() { + String options = ""; + double x1 = getFirstArg(); + double y1 = getNextArg(); + double x2 = getNextArg(); + double y2 = getNextArg(); + if (interp.nextToken()==',') + options = getNextString(); + interp.getRightParen(); + Arrow arrow = new Arrow(x1, y1, x2, y2); + arrow.setStyle(options); + getImage().setRoi(arrow); + } + + void makeOval() { + Roi previousRoi = getImage().getRoi(); + if (shiftKeyDown||altKeyDown) getImage().saveRoi(); + IJ.makeOval(getFirstArg(), getNextArg(), getNextArg(), getLastArg()); + Roi roi = getImage().getRoi(); + if (previousRoi!=null && roi!=null) + updateRoi(roi); + resetImage(); + shiftKeyDown = altKeyDown = false; + IJ.setKeyUp(IJ.ALL_KEYS); + } + + void makeRectangle() { + Roi previousRoi = getImage().getRoi(); + if (shiftKeyDown||altKeyDown) getImage().saveRoi(); + double x = getFirstArg(); + double y = getNextArg(); + double w = getNextArg(); + double h = getNextArg(); + int arcSize = 0; + if (interp.nextToken()==',') { + interp.getComma(); + arcSize = (int)interp.getExpression(); + } + interp.getRightParen(); + if (arcSize<1) + IJ.makeRectangle(x, y, w, h); + else { + ImagePlus imp = getImage(); + imp.setRoi(new Roi(x,y,w,h,arcSize)); + } + Roi roi = getImage().getRoi(); + if (previousRoi!=null && roi!=null) + updateRoi(roi); + resetImage(); + shiftKeyDown = altKeyDown = false; + IJ.setKeyUp(IJ.ALL_KEYS); + } + + void makeRotatedRectangle() { + getImage().setRoi(new RotatedRectRoi(getFirstArg(), getNextArg(), getNextArg(), getNextArg(), getLastArg())); + resetImage(); + } + + ImagePlus getImage() { + ImagePlus imp = IJ.getImage(interp); + if (imp.getWindow()==null && IJ.getInstance()!=null && !interp.isBatchMode() && WindowManager.getTempCurrentImage()==null) + throw new RuntimeException(Macro.MACRO_CANCELED); + defaultIP = null; + defaultImp = imp; + return imp; + } + + void resetImage() { + defaultImp = null; + defaultIP = null; + fontSet = false; + } + + ImageProcessor getProcessor() { + if (defaultIP==null) { + defaultIP = getImage().getProcessor(); + if (globalLineWidth>0) + defaultIP.setLineWidth(globalLineWidth); + if (globalColor!=null) + defaultIP.setColor(globalColor); + else if (!Double.isNaN(globalValue)) + defaultIP.setValue(globalValue); + else + defaultIP.setColor(Toolbar.getForegroundColor()); + } + return defaultIP; + } + + int getType() { + imageType = getImage().getType(); + return imageType; + } + + void setPixel() { + interp.getLeftParen(); + int a1 = (int)interp.getExpression(); + interp.getComma(); + double a2 = interp.getExpression(); + interp.getToken(); + ImageProcessor ip = getProcessor(); + if (interp.token==',') { + double a3 = interp.getExpression(); + interp.getRightParen(); + if (ip instanceof FloatProcessor) + ip.putPixelValue(a1, (int)a2, a3); + else + ip.putPixel(a1, (int)a2, (int)a3); + } else { + if (interp.token!=')') interp.error("')' expected"); + if (ip instanceof ColorProcessor) + ip.set(a1, (int)a2); + else + ip.setf(a1, (float)a2); + } + updateNeeded = true; + } + + double getPixel() { + interp.getLeftParen(); + double a1 = interp.getExpression(); + ImageProcessor ip = getProcessor(); + double value = 0.0; + interp.getToken(); + if (interp.token==',') { + double a2 = interp.getExpression(); + interp.getRightParen(); + int ia1 = (int)a1; + int ia2 = (int)a2; + if (a1==ia1 && a2==ia2) { + if (ip instanceof FloatProcessor) + value = ip.getPixelValue(ia1, ia2); + else + value = ip.getPixel(ia1, ia2); + } else { + if (ip instanceof ColorProcessor) + value = ip.getPixelInterpolated(a1, a2); + else { + ImagePlus imp = getImage(); + Calibration cal = imp.getCalibration(); + imp.setCalibration(null); + ip = imp.getProcessor(); + value = ip.getInterpolatedValue(a1, a2); + imp.setCalibration(cal); + } + } + } else { + if (interp.token!=')') interp.error("')' expected"); + if (ip instanceof ColorProcessor) + value = ip.get((int)a1); + else + value = ip.getf((int)a1); + } + return value; + } + + void setZCoordinate() { + int z = (int)getArg(); + int n = z + 1; + ImagePlus imp = getImage(); + ImageStack stack = imp.getStack(); + int size = stack.size(); + if (z<0 || z>=size) + interp.error("Z coordinate ("+z+") is out of 0-"+(size-1)+ " range"); + this.defaultIP = stack.getProcessor(n); + } + + void moveTo() { + interp.getLeftParen(); + int a1 = (int)Math.round(interp.getExpression()); + interp.getComma(); + int a2 = (int)Math.round(interp.getExpression()); + interp.getRightParen(); + getProcessor().moveTo(a1, a2); + } + + void lineTo() { + interp.getLeftParen(); + int a1 = (int)Math.round(interp.getExpression()); + interp.getComma(); + int a2 = (int)Math.round(interp.getExpression()); + interp.getRightParen(); + ImageProcessor ip = getProcessor(); + ip.lineTo(a1, a2); + updateAndDraw(); + } + + void drawLine() { + interp.getLeftParen(); + int x1 = (int)Math.round(interp.getExpression()); + interp.getComma(); + int y1 = (int)Math.round(interp.getExpression()); + interp.getComma(); + int x2 = (int)Math.round(interp.getExpression()); + interp.getComma(); + int y2 = (int)Math.round(interp.getExpression()); + interp.getRightParen(); + ImageProcessor ip = getProcessor(); + ip.drawLine(x1, y1, x2, y2); + updateAndDraw(); + } + + void doIPMethod(int type) { + interp.getParens(); + ImageProcessor ip = getProcessor(); + switch (type) { + case SNAPSHOT: ip.snapshot(); break; + case RESET: + ip.reset(); + updateNeeded = true; + break; + case FILL: + ImagePlus imp = getImage(); + Roi roi = imp.getRoi(); + if (roi==null) { + ip.resetRoi(); + ip.fill(); + } else { + ip.setRoi(roi); + ip.fill(ip.getMask()); + } + imp.updateAndDraw(); + break; + } + } + + void updateAndDraw() { + if (autoUpdate) { + ImagePlus imp = defaultImp; + if (imp==null) + imp = getImage(); + imp.updateChannelAndDraw(); + imp.changes = true; + } else + updateNeeded = true; + } + + void updateDisplay() { + if (updateNeeded && WindowManager.getImageCount()>0) { + ImagePlus imp = getImage(); + imp.updateAndDraw(); + updateNeeded = false; + } + } + + void drawString() { + interp.getLeftParen(); + String str = getString(); + interp.getComma(); + int x = (int)(interp.getExpression()+0.5); + interp.getComma(); + int y = (int)(interp.getExpression()+0.5); + Color background = null; + if (interp.nextToken()==',') { + interp.getComma(); + background = getColor(); + } + interp.getRightParen(); + ImageProcessor ip = getProcessor(); + setFont(ip); + ip.setJustification(justification); + ip.setAntialiasedText(antialiasedText); + if (background!=null) + ip.drawString(str, x, y, background); + else + ip.drawString(str, x, y); + updateAndDraw(); + } + + void setFont(ImageProcessor ip) { + if (font!=null && !fontSet) + ip.setFont(font); + fontSet = true; + } + + void setJustification() { + String str = getStringArg().toLowerCase(Locale.US); + int just = ImageProcessor.LEFT_JUSTIFY; + if (str.equals("center")) + just = ImageProcessor.CENTER_JUSTIFY; + else if (str.equals("right")) + just = ImageProcessor.RIGHT_JUSTIFY; + justification = just; + } + + void changeValues() { + double darg1 = getFirstArg(); + double darg2 = getNextArg(); + double darg3 = getLastArg(); + ImagePlus imp = getImage(); + ImageProcessor ip = getProcessor(); + Roi roi = imp.getRoi(); + ImageProcessor mask = null; + if (roi==null || !roi.isArea()) { + ip.resetRoi(); + roi = null; + } else { + ip.setRoi(roi); + mask = ip.getMask(); + if (mask!=null) ip.snapshot(); + } + int xmin=0, ymin=0, xmax=imp.getWidth(), ymax=imp.getHeight(); + if (roi!=null) { + Rectangle r = roi.getBounds(); + xmin=r.x; ymin=r.y; xmax=r.x+r.width; ymax=r.y+r.height; + } + boolean isFloat = getType()==ImagePlus.GRAY32; + if (imp.getBitDepth()==24) { + darg1 = (int)darg1&0xffffff; + darg2 = (int)darg2&0xffffff; + } + double v; + for (int y=ymin; y=darg1 && v<=darg2; + if (Double.isNaN(darg1) && Double.isNaN(darg2) && Double.isNaN(v)) + replace = true; + if (replace) { + if (isFloat) + ip.putPixelValue(x, y, darg3); + else + ip.putPixel(x, y, (int)darg3); + } + } + } + if (mask!=null) ip.reset(mask); + imp.updateAndDraw(); + updateNeeded = false; + } + + void requires() { + if (IJ.versionLessThan(getStringArg())) + interp.done = true; + } + + Random ran; + double random() { + double dseed = Double.NaN; + boolean gaussian = false; + if (interp.nextToken()=='(') { + interp.getLeftParen(); + if (isStringArg()) { + String arg = getString().toLowerCase(Locale.US); + if (arg.equals("seed")) { + interp.getComma(); + dseed = interp.getExpression(); + long seed = (long)dseed; + if (seed!=dseed) + interp.error("Seed not integer"); + ran = new Random(seed); + ImageProcessor.setRandomSeed(seed); + } else if (arg.equals("gaussian")) + gaussian = true; + else + interp.error("'seed' or ''gaussian' expected"); + } + interp.getRightParen(); + if (!Double.isNaN(dseed)) return Double.NaN; + } + ImageProcessor.setRandomSeed(Double.NaN); + interp.getParens(); + if (ran==null) + ran = new Random(); + if (gaussian) + return ran.nextGaussian(); + else + return ran.nextDouble(); + } + + double getResult(ResultsTable rt) { + interp.getLeftParen(); + String column = getString(); + int row = -1; + if (interp.nextToken()==',') { + interp.getComma(); + row = (int)interp.getExpression(); + } + if (interp.nextToken()==',') { + interp.getComma(); + String title = getString(); + rt = getResultsTable(title); + } + if (rt==null) + rt = getResultsTable(true); + interp.getRightParen(); + int counter = rt.size(); + if (row==-1) row = counter-1; + if (row<0 || row>=counter) + interp.error("Row ("+row+") out of range"); + int col = rt.getColumnIndex(column); + if (!rt.columnExists(col)) + return Double.NaN; + else { + double value = rt.getValueAsDouble(col, row); + if (Double.isNaN(value)) { + String s = rt.getStringValue(col, row); + if (s!=null && !s.equals("NaN")) + value = Tools.parseDouble(s); + } + return value; + } + } + + String getResultString(ResultsTable rt) { + interp.getLeftParen(); + String column = getString(); + int row = -1; + if (interp.nextToken()==',') { + interp.getComma(); + row = (int)interp.getExpression(); + } + if (interp.nextToken()==',') { + interp.getComma(); + String title = getString(); + rt = getResultsTable(title); + } + interp.getRightParen(); + if (rt==null) + rt = getResultsTable(true); + int counter = rt.size(); + if (row==-1) row = counter-1; + if (row<0 || row>=counter) + interp.error("Row ("+row+") out of range"); + int col = rt.getColumnIndex(column); + if (rt.columnExists(col)) + return rt.getStringValue(col, row); + else { + String label = null; + if ("Label".equals(column)) + label = rt.getLabel(row); + return label!=null?label:"null"; + } + } + + String getResultLabel() { + int row = (int)getArg(); + ResultsTable rt = getResultsTable(true); + int counter = rt.size(); + if (row<0 || row>=counter) + interp.error("Row ("+row+") out of range"); + String label = rt.getLabel(row); + if (label!=null) + return label; + else { + label = rt.getStringValue("Label", row); + return label!=null?label:""; + } + } + + private ResultsTable getResultsTable(boolean reportErrors) { + ResultsTable rt = Analyzer.getResultsTable(); + int size = rt.size(); + if (size==0) { + Frame frame = WindowManager.getFrontWindow(); + if (frame==null || (frame instanceof Editor)) + frame = WindowManager.getFrame("Results"); + if (frame!=null && (frame instanceof TextWindow)) { + TextPanel tp = ((TextWindow)frame).getTextPanel(); + rt = tp.getOrCreateResultsTable(); + size = rt!=null?rt.size():0; + } + } + if (size==0) { + Window win = WindowManager.getActiveTable(); + if (win!=null && (win instanceof TextWindow)) { + TextPanel tp = ((TextWindow)win).getTextPanel(); + rt = tp.getOrCreateResultsTable(); + size = rt!=null?rt.size():0; + } + } + if (size==0 && reportErrors) + interp.error("No results found"); + return rt; + } + + void setResult(ResultsTable rt) { + interp.getLeftParen(); + String column = getString(); + interp.getComma(); + int row = (int)interp.getExpression(); + interp.getComma(); + double value = 0.0; + String stringValue = null; + boolean isLabel = column.equals("Label"); + if (isStringArg() || isLabel) + stringValue = getString(); + else + value = interp.getExpression(); + if (interp.nextToken()==',') { + interp.getComma(); + String title = getString(); + rt = getResultsTable(title); + } + interp.getRightParen(); + if (rt==null) { + rt = Analyzer.getResultsTable(); + resultsPending = true; + } else + unUpdatedTable = rt; + if (row<0 || row>rt.size()) + interp.error("Row ("+row+") out of range"); + if (row==rt.size()) + rt.incrementCounter(); + try { + if (stringValue!=null) { + if (isLabel) + rt.setLabel(stringValue, row); + else + rt.setValue(column, row, stringValue); + } else + rt.setValue(column, row, value); + } catch (Exception e) { + interp.error(""+e.getMessage()); + } + } + + void updateResults() { + interp.getParens(); + ResultsTable rt = Analyzer.getResultsTable(); + rt.show("Results"); + resultsPending = false; + } + + double getNumber() { + String prompt = getFirstString(); + double defaultValue = getLastArg(); + String title = interp.macroName!=null?interp.macroName:""; + if (title.endsWith(" Options")) + title = title.substring(0, title.length()-8); + GenericDialog gd = new GenericDialog(title); + int decimalPlaces = (int)defaultValue==defaultValue?0:2; + gd.addNumericField(prompt, defaultValue, decimalPlaces); + gd.showDialog(); + if (gd.wasCanceled()) { + interp.done = true; + return defaultValue; + } + double v = gd.getNextNumber(); + if (gd.invalidNumber()) + return defaultValue; + else + return v; + } + + double getBoolean() { + interp.getLeftParen(); + String prompt = getString(); + String yesButton = " Yes "; + String noButton = " No "; + if (interp.nextToken()==',') { + yesButton = getNextString(); + noButton = getNextString(); + } + interp.getRightParen(); + String title = interp.macroName!=null?interp.macroName:""; + if (title.endsWith(" Options")) + title = title.substring(0, title.length()-8); + YesNoCancelDialog d = new YesNoCancelDialog(IJ.getInstance(), title, prompt, yesButton, noButton); + if (d.cancelPressed()) { + interp.done = true; + return 0.0; + } else if (d.yesPressed()) + return 1.0; + else + return 0.0; + } + + String getStringDialog() { + interp.getLeftParen(); + String prompt = getString(); + interp.getComma(); + String defaultStr = getString(); + interp.getRightParen(); + + String title = interp.macroName!=null?interp.macroName:""; + if (title.endsWith(" Options")) + title = title.substring(0, title.length()-8); + GenericDialog gd = new GenericDialog(title); + gd.addStringField(prompt, defaultStr, 20); + gd.showDialog(); + String str = ""; + if (gd.wasCanceled()) + interp.done = true; + else + str = gd.getNextString(); + return str; + } + + String d2s() { + return IJ.d2s(getFirstArg(), (int)getLastArg()); + } + + String toString(int base) { + int arg = (int)getArg(); + if (base==2) + return Integer.toBinaryString(arg); + else + return Integer.toHexString(arg); + } + + double getStackSize() { + interp.getParens(); + return getImage().getStackSize(); + } + + double getImageCount() { + interp.getParens(); + return WindowManager.getImageCount(); + } + + double getResultsCount() { + interp.getParens(); + return Analyzer.getResultsTable().getCounter(); + } + + void getCoordinates() { + ij.macro.Variable xCoordinates = getFirstArrayVariable(); + ij.macro.Variable yCoordinates = getLastArrayVariable(); + ImagePlus imp = getImage(); + Roi roi = imp.getRoi(); + if (roi==null) + interp.error("Selection required"); + ij.macro.Variable[] xa, ya; + if (roi.getType()==Roi.LINE) { + xa = new ij.macro.Variable[2]; + ya = new ij.macro.Variable[2]; + Line line = (Line)roi; + xa[0] = new ij.macro.Variable(line.x1d); + ya[0] = new ij.macro.Variable(line.y1d); + xa[1] = new ij.macro.Variable(line.x2d); + ya[1] = new ij.macro.Variable(line.y2d); + } else { + FloatPolygon fp = roi.getFloatPolygon(); + if (fp!=null) { + xa = new ij.macro.Variable[fp.npoints]; + ya = new ij.macro.Variable[fp.npoints]; + for (int i=0; i0 && s2!=null && (s2.equals(",")||s2.equals(";"))) + strings = s1.split(s2,-1); + else if (s1.length()>0 && s2!=null && s2.length()>=3 && s2.startsWith("(")&&s2.endsWith(")")) { + s2 = s2.substring(1,s2.length()-1); + strings = s1.split(s2,-1); + } else + strings = (s2==null||s2.equals(""))?Tools.split(s1):Tools.split(s1, s2); + ij.macro.Variable[] array = new ij.macro.Variable[strings.length]; + for (int i=0; i

+	*/
+	public DICOM(InputStream is) {
+		this(new BufferedInputStream(is));
+	}
+
+	/** Constructs a DICOM reader that using an BufferredInputStream. */
+	public DICOM(BufferedInputStream bis) {
+		inputStream = bis;
+	}
+
+	public void run(String arg) {
+		OpenDialog od = new OpenDialog("Open Dicom...", arg);
+		String directory = od.getDirectory();
+		String fileName = od.getFileName();
+		if (fileName==null)
+			return;
+		DicomDecoder dd = new DicomDecoder(directory, fileName);
+		dd.inputStream = inputStream;
+		FileInfo fi = null;
+		try {
+			fi = dd.getFileInfo();
+		} catch (IOException e) {
+			String msg = e.getMessage();
+			IJ.showStatus("");
+			if (msg.indexOf("EOF")<0&&showErrors) {
+				IJ.error("DICOM Reader", e.getClass().getName()+"\n \n"+msg);
+				return;
+			} else if (!dd.dicmFound()&&showErrors) {
+				msg = "This does not appear to be a valid\n"
+				+ "DICOM file. It does not have the\n"
+				+ "characters 'DICM' at offset 128.";
+				IJ.error("DICOM Reader", msg);
+				return;
+			}
+		}
+		if (gettingInfo) {
+			info = dd.getDicomInfo();
+			return;
+		}
+		if (fi!=null && fi.width>0 && fi.height>0 && fi.offset>0) {
+			FileOpener fo = new FileOpener(fi);
+			ImagePlus imp = fo.openImage();
+			// Avoid opening as float even if slope != 1.0 in case ignoreRescaleSlope or fixedDicomScaling
+			// were checked in the DICOM preferences.
+			boolean openAsFloat = (dd.rescaleSlope!=1.0 && !(Prefs.ignoreRescaleSlope || Prefs.fixedDicomScaling)) 
+				|| Prefs.openDicomsAsFloat;
+			String options = Macro.getOptions();
+			if (openAsFloat) {
+				IJ.run(imp, "32-bit", "");
+				if (dd.rescaleSlope!=1.0)
+					IJ.run(imp, "Multiply...", "value="+dd.rescaleSlope+" stack");
+				if (dd.rescaleIntercept!=0.0)
+					IJ.run(imp, "Add...", "value="+dd.rescaleIntercept+" stack");
+				if (imp.getStackSize()>1) {
+				    imp.setSlice(imp.getStackSize()/2);
+					ImageStatistics stats = imp.getRawStatistics();
+					imp.setDisplayRange(stats.min,stats.max);
+				}
+			} else if (fi.fileType==FileInfo.GRAY16_SIGNED) {
+				if (dd.rescaleIntercept!=0.0 && (dd.rescaleSlope==1.0||Prefs.fixedDicomScaling)) {
+					double[] coeff = new double[2];
+					coeff[0] = dd.rescaleSlope*(-32768) + dd.rescaleIntercept;
+					coeff[1] = dd.rescaleSlope;
+					imp.getCalibration().setFunction(Calibration.STRAIGHT_LINE, coeff, "Gray Value");
+				}
+			} else if (dd.rescaleIntercept!=0.0 && 
+					  (dd.rescaleSlope==1.0||Prefs.fixedDicomScaling||fi.fileType==FileInfo.GRAY8)) {
+				double[] coeff = new double[2];
+				coeff[0] = dd.rescaleIntercept;
+				coeff[1] = dd.rescaleSlope;
+				imp.getCalibration().setFunction(Calibration.STRAIGHT_LINE, coeff, "Gray Value");
+			}
+			Macro.setOptions(options);
+			if (dd.windowWidth>0.0) {
+				double min = dd.windowCenter-dd.windowWidth/2;
+				double max = dd.windowCenter+dd.windowWidth/2;
+				if (!openAsFloat) {
+					Calibration cal = imp.getCalibration();
+					min = cal.getRawValue(min);
+					max = cal.getRawValue(max);
+				}
+				ImageProcessor ip = imp.getProcessor();
+				ip.setMinAndMax(min, max);
+				if (IJ.debugMode) IJ.log("window: "+min+"-"+max);
+			}
+			if (imp.getStackSize()>1)
+				setStack(fileName, imp.getStack());
+			else
+				setProcessor(fileName, imp.getProcessor());
+			setCalibration(imp.getCalibration());
+			setProperty("Info", dd.getDicomInfo());
+			setFileInfo(fi); // needed for revert
+			if (arg.equals("")) show();
+		} else if (showErrors)
+			IJ.error("DICOM Reader","Unable to decode DICOM header.");
+		IJ.showStatus("");
+	}
+
+	/** Opens the specified file as a DICOM. Does not 
+		display a message if there is an error.
+		Here is an example:
+		
+		DICOM dcm = new DICOM();
+		dcm.open(path);
+		if (dcm.getWidth()==0)
+			IJ.log("Error opening '"+path+"'");
+		else
+			dcm.show();
+		
+ */ + public void open(String path) { + showErrors = false; + run(path); + } + + /** Returns the DICOM tags of the specified file as a string. */ + public String getInfo(String path) { + showErrors = false; + gettingInfo = true; + run(path); + return info; + } + + /** Convert 16-bit signed to unsigned if all pixels>=0. */ + void convertToUnsigned(ImagePlus imp, FileInfo fi) { + ImageProcessor ip = imp.getProcessor(); + short[] pixels = (short[])ip.getPixels(); + int min = Integer.MAX_VALUE; + int value; + for (int i=0; i=32768) { + for (int i=0; i60) + s = s.substring(0,60); + return s; + } + + int getByte() throws IOException { + int b = f.read(); + if (b ==-1) + throw new IOException("unexpected EOF"); + ++location; + return b; + } + + int getShort() throws IOException { + int b0 = getByte(); + int b1 = getByte(); + if (littleEndian) + return ((b1 << 8) + b0); + else + return ((b0 << 8) + b1); + } + + int getSShort() throws IOException { + short b0 = (short)getByte(); + short b1 = (short)getByte(); + if (littleEndian) + return ((b1 << 8) + b0); + else + return ((b0 << 8) + b1); + } + + final int getInt() throws IOException { + int b0 = getByte(); + int b1 = getByte(); + int b2 = getByte(); + int b3 = getByte(); + if (littleEndian) + return ((b3<<24) + (b2<<16) + (b1<<8) + b0); + else + return ((b0<<24) + (b1<<16) + (b2<<8) + b3); + } + + long getUInt() throws IOException { + long b0 = getByte(); + long b1 = getByte(); + long b2 = getByte(); + long b3 = getByte(); + if (littleEndian) + return ((b3<<24) + (b2<<16) + (b1<<8) + b0); + else + return ((b0<<24) + (b1<<16) + (b2<<8) + b3); + } + + double getDouble() throws IOException { + int b0 = getByte(); + int b1 = getByte(); + int b2 = getByte(); + int b3 = getByte(); + int b4 = getByte(); + int b5 = getByte(); + int b6 = getByte(); + int b7 = getByte(); + long res = 0; + if (littleEndian) { + res += b0; + res += ( ((long)b1) << 8); + res += ( ((long)b2) << 16); + res += ( ((long)b3) << 24); + res += ( ((long)b4) << 32); + res += ( ((long)b5) << 40); + res += ( ((long)b6) << 48); + res += ( ((long)b7) << 56); + } else { + res += b7; + res += ( ((long)b6) << 8); + res += ( ((long)b5) << 16); + res += ( ((long)b4) << 24); + res += ( ((long)b3) << 32); + res += ( ((long)b2) << 40); + res += ( ((long)b1) << 48); + res += ( ((long)b0) << 56); + } + return Double.longBitsToDouble(res); + } + + float getFloat() throws IOException { + int b0 = getByte(); + int b1 = getByte(); + int b2 = getByte(); + int b3 = getByte(); + int res = 0; + if (littleEndian) { + res += b0; + res += ( ((long)b1) << 8); + res += ( ((long)b2) << 16); + res += ( ((long)b3) << 24); + } else { + res += b3; + res += ( ((long)b2) << 8); + res += ( ((long)b1) << 16); + res += ( ((long)b0) << 24); + } + return Float.intBitsToFloat(res); + } + + byte[] getLut(int length) throws IOException { + if ((length&1)!=0) { // odd + String dummy = getString(length); + return null; + } + length /= 2; + byte[] lut = new byte[length]; + for (int i=0; i>>8); + return lut; + } + + int getLength() throws IOException { + int b0 = getByte(); + int b1 = getByte(); + int b2 = getByte(); + int b3 = getByte(); + + // We cannot know whether the VR is implicit or explicit + // without the full DICOM Data Dictionary for public and + // private groups. + + // We will assume the VR is explicit if the two bytes + // match the known codes. It is possible that these two + // bytes are part of a 32-bit length for an implicit VR. + + vr = (b0<<8) + b1; + + switch (vr) { + case OB: case OW: case SQ: case UN: case UT: + case OF: case OL: case OD: case UC: case UR: + case OV: case SV: case UV: + // Explicit VR with 32-bit length if other two bytes are zero + if ( (b2 == 0) || (b3 == 0) ) return getInt(); + // Implicit VR with 32-bit length + vr = IMPLICIT_VR; + if (littleEndian) + return ((b3<<24) + (b2<<16) + (b1<<8) + b0); + else + return ((b0<<24) + (b1<<16) + (b2<<8) + b3); + case AE: case AS: case AT: case CS: case DA: case DS: case DT: case FD: + case FL: case IS: case LO: case LT: case PN: case SH: case SL: case SS: + case ST: case TM: case UI: case UL: case US: case QQ: + // Explicit vr with 16-bit length + if (littleEndian) + return ((b3<<8) + b2); + else + return ((b2<<8) + b3); + default: + // Implicit VR with 32-bit length... + vr = IMPLICIT_VR; + if (littleEndian) + return ((b3<<24) + (b2<<16) + (b1<<8) + b0); + else + return ((b0<<24) + (b1<<16) + (b2<<8) + b3); + } + } + + int getNextTag() throws IOException { + int groupWord = getShort(); + if (groupWord==0x0800 && bigEndianTransferSyntax) { + littleEndian = false; + groupWord = 0x0008; + } + int elementWord = getShort(); + int tag = groupWord<<16 | elementWord; + elementLength = getLength(); + + // hack needed to read some GE files + // The element length must be even! + if (elementLength==13 && !oddLocations) elementLength = 10; + + // "Undefined" element length. + // This is a sort of bracket that encloses a sequence of elements. + if (elementLength==-1) { + elementLength = 0; + inSequence = true; + } + //IJ.log("getNextTag: "+tag+" "+elementLength); + return tag; + } + + FileInfo getFileInfo() throws IOException { + long skipCount; + FileInfo fi = new FileInfo(); + int bitsAllocated = 16; + fi.fileFormat = fi.RAW; + fi.fileName = fileName; + if (directory.indexOf("://")>0) { // is URL + URL u = new URL(directory+fileName); + inputStream = new BufferedInputStream(u.openStream()); + fi.inputStream = inputStream; + } else if (inputStream!=null) + fi.inputStream = inputStream; + else + fi.directory = directory; + fi.width = 0; + fi.height = 0; + fi.offset = 0; + fi.intelByteOrder = true; + fi.fileType = FileInfo.GRAY16_UNSIGNED; + fi.fileFormat = FileInfo.DICOM; + int samplesPerPixel = 1; + int planarConfiguration = 0; + String photoInterpretation = ""; + + if (inputStream!=null) { + // Use large buffer to allow URL stream to be reset after reading header + f = inputStream; + f.mark(400000); + } else + f = new BufferedInputStream(new FileInputStream(directory + fileName)); + if (IJ.debugMode) { + IJ.log(""); + IJ.log("DicomDecoder: decoding "+fileName); + } + + int[] bytes = new int[ID_OFFSET]; + for (int i=0; i-1||s.indexOf("1.2.5")>-1) { + f.close(); + String msg = "ImageJ cannot open compressed DICOM images.\n \n"; + msg += "Transfer Syntax UID = "+s; + throw new IOException(msg); + } + if (s.indexOf("1.2.840.10008.1.2.2")>=0) + bigEndianTransferSyntax = true; + break; + case MODALITY: + modality = getString(elementLength); + addInfo(tag, modality); + break; + case NUMBER_OF_FRAMES: + s = getString(elementLength); + addInfo(tag, s); + double frames = s2d(s); + if (frames>1.0) + fi.nImages = (int)frames; + break; + case SAMPLES_PER_PIXEL: + samplesPerPixel = getShort(); + addInfo(tag, samplesPerPixel); + break; + case PHOTOMETRIC_INTERPRETATION: + photoInterpretation = getString(elementLength); + addInfo(tag, photoInterpretation); + break; + case PLANAR_CONFIGURATION: + planarConfiguration = getShort(); + addInfo(tag, planarConfiguration); + break; + case ROWS: + fi.height = getShort(); + addInfo(tag, fi.height); + break; + case COLUMNS: + fi.width = getShort(); + addInfo(tag, fi.width); + break; + case IMAGER_PIXEL_SPACING: case PIXEL_SPACING: + String scale = getString(elementLength); + getSpatialScale(fi, scale); + addInfo(tag, scale); + break; + case SLICE_THICKNESS: case SLICE_SPACING: + String spacing = getString(elementLength); + fi.pixelDepth = s2d(spacing); + addInfo(tag, spacing); + break; + case BITS_ALLOCATED: + bitsAllocated = getShort(); + if (bitsAllocated==8) + fi.fileType = FileInfo.GRAY8; + else if (bitsAllocated==32) + fi.fileType = FileInfo.GRAY32_UNSIGNED; + addInfo(tag, bitsAllocated); + break; + case PIXEL_REPRESENTATION: + int pixelRepresentation = getShort(); + if (pixelRepresentation==1) { + fi.fileType = FileInfo.GRAY16_SIGNED; + signed = true; + } + addInfo(tag, pixelRepresentation); + break; + case WINDOW_CENTER: + String center = getString(elementLength); + int index = center.indexOf('\\'); + if (index!=-1) center = center.substring(index+1); + windowCenter = s2d(center); + addInfo(tag, center); + break; + case WINDOW_WIDTH: + String width = getString(elementLength); + index = width.indexOf('\\'); + if (index!=-1) width = width.substring(index+1); + windowWidth = s2d(width); + addInfo(tag, width); + break; + case RESCALE_INTERCEPT: + String intercept = getString(elementLength); + rescaleIntercept = s2d(intercept); + addInfo(tag, intercept); + break; + case RESCALE_SLOPE: + String slop = getString(elementLength); + rescaleSlope = s2d(slop); + addInfo(tag, slop); + break; + case RED_PALETTE: + fi.reds = getLut(elementLength); + addInfo(tag, elementLength/2); + break; + case GREEN_PALETTE: + fi.greens = getLut(elementLength); + addInfo(tag, elementLength/2); + break; + case BLUE_PALETTE: + fi.blues = getLut(elementLength); + addInfo(tag, elementLength/2); + break; + case FLOAT_PIXEL_DATA: + fi.fileType = FileInfo.GRAY32_FLOAT; + // continue without break + case PIXEL_DATA: + // Start of image data... + if (elementLength!=0) { + fi.offset = location; + addInfo(tag, location); + decodingTags = false; + } else + addInfo(tag, null); + break; + case 0x7F880010: + // What is this? - RAK + if (elementLength!=0) { + fi.offset = location+4; + decodingTags = false; + } + break; + default: + // Not used, skip over it... + addInfo(tag, null); + } + } // while(decodingTags) + + if (fi.fileType==FileInfo.GRAY8) { + if (fi.reds!=null && fi.greens!=null && fi.blues!=null + && fi.reds.length==fi.greens.length + && fi.reds.length==fi.blues.length) { + fi.fileType = FileInfo.COLOR8; + fi.lutSize = fi.reds.length; + + } + } + + if (fi.fileType==FileInfo.GRAY32_UNSIGNED && signed) + fi.fileType = FileInfo.GRAY32_INT; + + if (samplesPerPixel==3 && photoInterpretation.startsWith("RGB")) { + if (planarConfiguration==0) + fi.fileType = FileInfo.RGB; + else if (planarConfiguration==1) + fi.fileType = FileInfo.RGB_PLANAR; + } else if (photoInterpretation.endsWith("1 ")) + fi.whiteIsZero = true; + + if (!littleEndian) + fi.intelByteOrder = false; + + if (IJ.debugMode) { + IJ.log("width: " + fi.width); + IJ.log("height: " + fi.height); + IJ.log("images: " + fi.nImages); + IJ.log("bits allocated: " + bitsAllocated); + IJ.log("offset: " + fi.offset); + } + + if (inputStream!=null) + f.reset(); + else + f.close(); + return fi; + } + + String getDicomInfo() { + String s = new String(dicomInfo); + char[] chars = new char[s.length()]; + s.getChars(0, s.length(), chars, 0); + for (int i=0; i>>16; + //if (group!=previousGroup && (previousInfo!=null&&previousInfo.indexOf("Sequence:")==-1)) + // dicomInfo.append("\n"); + previousGroup = group; + previousInfo = info; + dicomInfo.append(tag2hex(tag)+info+"\n"); + } + if (IJ.debugMode) { + if (info==null) info = ""; + vrLetters[0] = (byte)(vr >> 8); + vrLetters[1] = (byte)(vr & 0xFF); + String VR = new String(vrLetters); + IJ.log("(" + tag2hex(tag) + VR + + " " + elementLength + + " bytes from " + + (location-elementLength)+") " + + info); + } + } + + void addInfo(int tag, int value) throws IOException { + addInfo(tag, Integer.toString(value)); + } + + String getHeaderInfo(int tag, String value) throws IOException { + if (tag==ITEM_DELIMINATION || tag==SEQUENCE_DELIMINATION) { + inSequence = false; + if (!IJ.debugMode) return null; + } + String key = i2hex(tag); + //while (key.length()<8) + // key = '0' + key; + String id = (String)dictionary.get(key); + if (id!=null) { + if (vr==IMPLICIT_VR && id!=null) + vr = (id.charAt(0)<<8) + id.charAt(1); + id = id.substring(2); + } + if (tag==ITEM) + return id!=null?id+":":null; + if (value!=null) + return id+": "+value; + switch (vr) { + case FD: + if (elementLength==8) + value = Double.toString(getDouble()); + else + for (int i=0; i44) value=null; + break; + case SQ: + value = ""; + if (tag==ACQUISITION_CONTEXT_SEQUENCE) + acquisitionSequence = true; + if (tag==VIEW_CODE_SEQUENCE) + acquisitionSequence = false; + boolean privateTag = ((tag>>16)&1)!=0; + if (tag!=ICON_IMAGE_SEQUENCE && !privateTag) + break; + // else fall through and skip icon image sequence or private sequence + default: + long skipCount = (long)elementLength; + while (skipCount > 0) skipCount -= f.skip(skipCount); + location += elementLength; + value = ""; + } + if (value!=null && id==null && !value.equals("")) + return "---: "+value; + else if (id==null) + return null; + else + return id+": "+value; + } + + static char[] buf8 = new char[8]; + + /** Converts an int to an 8 byte hex string. */ + String i2hex(int i) { + for (int pos=7; pos>=0; pos--) { + buf8[pos] = Tools.hexDigits[i&0xf]; + i >>>= 4; + } + return new String(buf8); + } + + char[] buf10; + + String tag2hex(int tag) { + if (buf10==null) { + buf10 = new char[11]; + buf10[4] = ','; + buf10[9] = ' '; + } + int pos = 8; + while (pos>=0) { + buf10[pos] = Tools.hexDigits[tag&0xf]; + tag >>>= 4; + pos--; + if (pos==4) pos--; // skip coma + } + return new String(buf10); + } + + double s2d(String s) { + if (s==null) return 0.0; + if (s.startsWith("\\")) + s = s.substring(1); + Double d; + try {d = Double.valueOf(s);} + catch (NumberFormatException e) {d = null;} + if (d!=null) + return(d.doubleValue()); + else + return(0.0); + } + + void getSpatialScale(FileInfo fi, String scale) { + double xscale=0, yscale=0; + int i = scale.indexOf('\\'); + if (i>0) { + yscale = s2d(scale.substring(0, i)); + xscale = s2d(scale.substring(i+1)); + } + if (xscale!=0.0 && yscale!=0.0) { + fi.pixelWidth = xscale; + fi.pixelHeight = yscale; + fi.unit = "mm"; + } + } + + boolean dicmFound() { + return dicmFound; + } + +} + + +class DicomDictionary { + + Properties getDictionary() { + Properties p = new Properties(); + for (int i=0; i1) { + for (int i=0; i=limit && !IJ.is64Bit()) { + if (!IJ.showMessageWithCancel(title, + "Note: setting the memory limit to a value\n" + +"greater than "+limit+"MB on a 32-bit system\n" + +"may cause ImageJ to fail to start. The title of\n" + +"the Edit>Options>Memory & Threads dialog\n" + +"box changes to \"Memory (64-bit)\" when ImageJ\n" + +"is running on a 64-bit version of Java.")) + return; + } + try { + String s2 = s.substring(index2); + if (s2.startsWith("g")) + s2 = "m"+s2.substring(1); + String s3 = s.substring(0, index1) + max2 + s2; + FileOutputStream fos = new FileOutputStream(f); + PrintWriter pw = new PrintWriter(fos); + pw.print(s3); + pw.close(); + } catch (IOException e) { + String error = e.getMessage(); + if (error==null || error.equals("")) error = ""+e; + String name = IJ.isMacOSX()?"Info.plist":"ImageJ.cfg"; + String msg = + "Unable to update the file \"" + name + "\".\n" + + " \n" + + "\"" + error + "\""; + IJ.showMessage("Memory", msg); + return; + } + String hint = ""; + if (IJ.isWindows() && max2>640 && max2>max) + hint = "\nDelete the \"ImageJ.cfg\" file, located in the ImageJ folder,\nif ImageJ fails to start."; + IJ.showMessage("Memory", "The new " + max2 +"MB limit will take effect after ImageJ is restarted."+hint); + } + + public long getMemorySetting() { + if (IJ.getApplet()!=null) return 0L; + long max = 0L; + if (IJ.isMacOSX()) { + String appPath = System.getProperty("java.class.path"); + if (appPath==null) return 0L; + int index = appPath.indexOf(".app/"); + if (index==-1) return 0L; + appPath = appPath.substring(0,index+5); + max = getMemorySetting(appPath+"Contents/Info.plist"); + } else + max = getMemorySetting("ImageJ.cfg"); + return max; + } + + void showError() { + int max = (int)(maxMemory()/1048576L); + String msg = + "ImageJ is unable to change the memory limit. For \n" + + "more information, refer to the installation notes at\n \n" + + " "+IJ.URL2+"/docs/install/\n" + + " \n"; + if (fileMissing) { + if (IJ.isMacOSX()) + msg += "The ImageJ application (ImageJ.app) was not found.\n \n"; + else if (IJ.isWindows()) + msg += "ImageJ.cfg not found.\n \n"; + fileMissing = false; + } + if (max>0) + msg += "Current limit: " + max + "MB"; + IJ.showMessage("Memory", msg); + } + + long getMemorySetting(String file) { + String path = file.startsWith("/")?file:Prefs.getImageJDir()+file; + if (IJ.debugMode) IJ.log("getMemorySetting: "+path); + f = new File(path); + if (!f.exists()) { + fileMissing = true; + return 0L; + } + long max = 0L; + try { + int size = (int)f.length(); + byte[] buffer = new byte[size]; + FileInputStream in = new FileInputStream(f); + in.read(buffer, 0, size); + s = new String(buffer, 0, size, "ISO8859_1"); + in.close(); + index1 = s.indexOf("-mx"); + if (index1==-1) index1 = s.indexOf("-Xmx"); + if (index1==-1) return 0L; + if (s.charAt(index1+1)=='X') index1+=4; else index1+=3; + index2 = index1; + while (index2 getApplets(); + + void showDocument(URL url); + + public void showDocument(URL url, String target); + + void showStatus(String status); + + public void setStream(String key, InputStream stream) throws IOException; + + public InputStream getStream(String key); + + public Iterator getStreamKeys(); +} diff --git a/mrj/26/ij/stub/AppletStub.java b/mrj/26/ij/stub/AppletStub.java new file mode 100644 index 00000000..b5e9a4e7 --- /dev/null +++ b/mrj/26/ij/stub/AppletStub.java @@ -0,0 +1,17 @@ +package ij.stub; + +import java.net.URL; + +public interface AppletStub { + boolean isActive(); + + URL getDocumentBase(); + + URL getCodeBase(); + + String getParameter(String name); + + AppletContext getAppletContext(); + + void appletResize(int width, int height); +} diff --git a/mrj/26/ij/stub/AudioClip.java b/mrj/26/ij/stub/AudioClip.java new file mode 100644 index 00000000..4e24db10 --- /dev/null +++ b/mrj/26/ij/stub/AudioClip.java @@ -0,0 +1,9 @@ +package ij.stub; + +public interface AudioClip { + void play(); + + void loop(); + + void stop(); +} diff --git a/pom.xml b/pom.xml index 2225bd96..ba9381a6 100644 --- a/pom.xml +++ b/pom.xml @@ -98,6 +98,8 @@ true + + 3.15.0 @@ -136,6 +138,7 @@ maven-compiler-plugin + ${maven-compiler-plugin.version} default-compile @@ -147,6 +150,7 @@ tests/** + mrj/** 9 @@ -161,6 +165,7 @@ **/module-info.java tests/** + mrj/** + true + + @@ -180,6 +210,16 @@ 1.6 + + maven-jar-plugin + + + + true + + + + maven-javadoc-plugin