/* * PathField.java * (FScape) * * Copyright (c) 2001-2016 Hanns Holger Rutz. All rights reserved. * * This software is published under the GNU General Public License v3+ * * * For further information, please contact Hanns Holger Rutz at * contact@sciss.de * * * Changelog: * 07-Jan-05 fillStream( AudioStream ) sets type field * 21-May-05 fillStream( AudioFileDescr ) will write the file field */ package de.sciss.fscape.gui; import de.sciss.app.BasicEvent; import de.sciss.app.EventManager; import de.sciss.fscape.Application; import de.sciss.fscape.io.GenericFile; import de.sciss.fscape.io.ImageStream; import de.sciss.fscape.spect.SpectStream; import de.sciss.fscape.util.Util; import de.sciss.gui.ColouredTextField; import de.sciss.gui.PathEvent; import de.sciss.gui.PathListener; import de.sciss.io.AudioFileDescr; import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ComponentEvent; import java.awt.event.ComponentListener; import java.awt.event.InputEvent; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.awt.event.KeyEvent; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.File; import java.io.IOException; import java.util.Vector; /** * GUI component containing a path string, * a path selector icon (which is also a * drag target for the Finder) and optional * info fields and type selectors. */ public class PathField extends JPanel implements ActionListener, ComponentListener, ItemListener, PathListener, EventManager.Processor { // -------- public variables -------- public static final int TYPE_BASICMASK = 0x0F; /** * Datei zum Einlesen */ public static final int TYPE_INPUTFILE = 0; /** * OR fuer TYPE_INPUTFILE/OUTPUT_FILE */ public static final int TYPE_FORMATFIELD= 0x10; /** * OR fuer TYPE_OUTPUT_FILE */ public static final int TYPE_RESFIELD = 0x20; public static final int TYPE_RATEFIELD = 0x40; /** * Dateiname fuer Schreibdatei */ public static final int TYPE_OUTPUTFILE = 1; /** * Directory */ public static final int TYPE_FOLDER = 2; public static final int SNDRES_16 = 0; public static final int SNDRES_24 = 1; public static final int SNDRES_32F = 2; public static final int SNDRES_32 = 3; public static final int SNDRES_NUM = 4; public static final int SNDRATE_96 = 0; public static final int SNDRATE_48 = 1; public static final int SNDRATE_44 = 2; public static final int SNDRATE_32 = 3; public static final int SNDRATE_NUM = 4; public static final int IMGRES_8 = 0; public static final int IMGRES_16 = 1; // -------- private variables -------- private static final float[] sndRate = { 96000.0f, 48000.0f, 44100.0f, 32000.0f }; // private static final float[] movRate = { 8.0f, 10.0f, 12.0f, 15.0f, 24.0f, 25.0f, 29.97f, 30.0f }; private static final float[] spectRate = sndRate; private static final String[] sndResID = { "int16", "int24", "int32", "float32" }; private static final String[] sndResTxt = { "16-bit int", "24-bit int", "32-bit float", "32-bit int" }; private static final String[] sndRateTxt= { "96 kHz", "48 kHz", "44.1 kHz", "32 kHz" }; private static final String[] spectRateTxt= sndRateTxt; private static final String[] imgResTxt = { "8-bit", "16-bit" }; private static final String[] movRateTxt= { "8 fps", "10 fps", "12 fps", "15 fps", "24 fps", "25 fps", "29.97 fps", "30 fps" }; private IOTextField ggPath; // private PathIcon ggChoose; private SelectPathButton ggChoose; private ColouredTextField ggFormat = null; private VirtualChoice ggType = null; private VirtualChoice ggRes = null; private VirtualChoice ggRate = null; private static final Color COLOR_ERR = new Color( 0xFF, 0x00, 0x00, 0x2F ); private static final Color COLOR_EXISTS= new Color( 0x00, 0x00, 0xFF, 0x2F ); private static final Color COLOR_PROPSET=new Color( 0x00, 0xFF, 0x00, 0x2F ); // private static final int MAXRESNUM = 3; // private static final int MAXRATENUM = 4; // protected Vector collListeners = new Vector(); protected final Vector<PathField> collChildren = new Vector<PathField>(); private int type; private int handledTypes[] = null; // the ones that we can handle (auto setFormat) // private String dlgTxt; private String scheme; private String protoScheme; private PathField superPaths[]; private boolean init = true; private boolean enabled = true; // constants for abbreviate protected static final int ABBR_LENGTH = 12; private final EventManager elm = new EventManager(this); // -------- public methods -------- public PathField(int type, String dlgTxt) { super(); this.type = type; // this.dlgTxt = dlgTxt; final GridBagLayout lay = new GridBagLayout(); final GridBagConstraints con = new GridBagConstraints(); ggPath = new IOTextField(); ggChoose = new SelectPathButton(type, dlgTxt); ggChoose.addPathListener( this ); setLayout(lay); con.fill = GridBagConstraints.HORIZONTAL; con.anchor = GridBagConstraints.WEST; // con.gridheight = 1; con.gridwidth = GridBagConstraints.RELATIVE; con.gridy = 1; con.weightx = 1.0; con.weighty = 0.0; lay.setConstraints(ggPath, con); ggPath.addActionListener( this ); // High-Level Events: Return-Hit weiterleiten add(ggPath); if ((type & TYPE_FORMATFIELD) != 0) { con.gridx = 0; con.gridy = 2; con.gridwidth = 1; if( (type & TYPE_BASICMASK) == TYPE_INPUTFILE ) { ggFormat = new ColouredTextField(); ggFormat.setEditable (false); ggFormat.setFocusable(false); // ggFormat.setBackground( null ); // ggFormat.setPaint( null ); // con.gridheight = 1; lay.setConstraints(ggFormat, con); add(ggFormat); } else { ggType = new VirtualChoice(); con.weightx = 0.3; lay.setConstraints(ggType, con); add(ggType); // ggType.addItemListener( this ); ggType.addSpecialItemListener(this); if ((type & TYPE_RESFIELD) != 0) { ggRes = new VirtualChoice(); con.gridx++; lay.setConstraints(ggRes, con); add(ggRes); // ggRes.addItemListener( this ); } if ((type & TYPE_RATEFIELD) != 0) { ggRate = new VirtualChoice(); con.gridx++; lay.setConstraints(ggRate, con); add(ggRate); // ggRate.addItemListener( this ); } } con.gridx++; con.gridheight = 3; con.anchor = GridBagConstraints.NORTHWEST; } else { con.gridheight = 2; } // con.gridwidth = GridBagConstraints.REMAINDER; con.gridy = 0; con.weightx = 0.0; con.weighty = 1.0; lay.setConstraints(ggChoose, con); add(ggChoose); // final JButton ggTest = new JButton(">"); // con.gridx++; // lay.setConstraints(ggChoose, con); // add(ggTest); deriveFrom(new PathField[0], (ggType != null) ? "$E" : ""); addComponentListener(this); this.addPropertyChangeListener("font", new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent e) { Font fnt = ((PathField) e.getSource()).getFont(); ggPath.setFont(fnt); if (ggFormat != null) ggFormat.setFont(fnt); } }); } public static String getSoundResID(int idx) { return sndResID[idx]; } public static int getSoundResIdx(String id) { for (int idx = 0; idx < sndResID.length; idx++) { if (sndResID[idx].equals(id)) return idx; } return -1; } public static String getSoundResDescr(int idx) { return sndResTxt[idx]; } public static String getSoundRateID(int idx) { return String.valueOf(sndRate[idx]); } public static int getSoundRateIdx(String id) { for (int idx = 0; idx < sndRate.length; idx++) { if (String.valueOf(sndRate[idx]).equals(id)) return idx; } return -1; } public static String getSoundRateDescr(int idx) { return sndRateTxt[idx]; } public void setPath(File path) { setPathIgnoreScheme(path); scheme = createScheme(path.getPath()); } protected void setPathIgnoreScheme(File path) { ggPath.setText(path.getPath()); ggChoose.setPath(path); synchronized (collChildren) { for (int i = 0; i < collChildren.size(); i++) { collChildren.get(i).motherSpeaks(path); } } feedback(); } protected void setPathAndDispatchEvent(File path) { setPathIgnoreScheme(path); elm.dispatchEvent(new PathEvent(this, PathEvent.CHANGED, System.currentTimeMillis(), path)); } public File getPath() { return (new File(ggPath.getText())); } public void setFormat(String txt) { if (ggFormat != null) { ggFormat.setText(txt); } } public String getFormat() { if (ggFormat != null) { return ggFormat.getText(); } else { return null; } } /** * Fill AudioFileDescr or ImageStream with * data corresponding to Resolution + Rate */ public void fillStream(AudioFileDescr afd) { int idx; afd.file = getPath(); if (ggType != null) { afd.type = GenericFile.getAudioFileType(getType()); } if (ggRes != null) { idx = ggRes.getSelectedIndex(); switch (idx) { case SNDRES_16: afd.bitsPerSample = 16; afd.sampleFormat = AudioFileDescr.FORMAT_INT; break; case SNDRES_24: afd.bitsPerSample = 24; afd.sampleFormat = AudioFileDescr.FORMAT_INT; break; case SNDRES_32F: afd.bitsPerSample = 32; afd.sampleFormat = AudioFileDescr.FORMAT_FLOAT; break; case SNDRES_32: afd.bitsPerSample = 32; afd.sampleFormat = AudioFileDescr.FORMAT_INT; break; } } if (ggRate != null) { idx = ggRate.getSelectedIndex(); if ((idx >= 0) && (idx < sndRate.length)) { afd.rate = sndRate[idx]; } } } /** * Fill AudioFileDescr or ImageStream with * data corresponding to Resolution + Rate */ public void fillStream(SpectStream stream) { int ID; if (ggRate != null) { ID = ggRate.getSelectedIndex(); if ((ID >= 0) && (ID < spectRate.length)) { stream.smpRate = spectRate[ID]; } } } public void fillStream(ImageStream stream) { int ID; if (ggRes != null) { ID = ggRes.getSelectedIndex(); switch (ID) { case IMGRES_8: stream.bitsPerSmp = 8; break; case IMGRES_16: stream.bitsPerSmp = 16; break; } } } public int getType() { if (ggType != null) { return GenericFile.getType(ggType.getSelectedItem().toString()); } else { return GenericFile.MODE_GENERIC; } } public JComboBox getTypeGadget() { return ggType; } public JComboBox getResGadget() { return ggRes ; } public JComboBox getRateGadget() { return ggRate; } /** * PathField offers a mechanism to automatically derive * a path name from a "mother" PathField. This applies * usually to output files whose names are derived from * PathFields which represent input paths. The provided * 'scheme' String can contain the Tags * * $Dx = Directory of superPath x; $Fx = Filename; $E = Extension; $Bx = Brief filename * * where 'x' is the index in the provided array of * mother PathFields. Whenever the mother contents * changes, the child PathField will recalculate its * name. When the user changes the contents of the child * PathField, an algorithm tries to find out which components * are related to the mother's pathname, parts that cannot * be identified will not be automatically changing any more * unless the user completely clears the PathField (i.e. * restores full automation). * * The user can abbreviate or extend filenames by pressing the appropriate * key; in this case the $F and $B tags are exchanged in the scheme. */ public void deriveFrom(PathField[] superPth, String schm) { superPaths = superPth; scheme = schm; protoScheme = schm; for (int i = 0; i < superPth.length; i++) { superPth[i].addChildPathField(this); } } protected void addChildPathField(PathField child) { synchronized (collChildren) { if (!collChildren.contains(child)) collChildren.add(child); } // synchronized( collChildren ) } protected void motherSpeaks(File superPath) { setPathAndDispatchEvent(new File(evalScheme(scheme))); } public void setEnabled(boolean state) { if (state == enabled) return; enabled = state; if (!state) { ggChoose.requestFocus(); // tricky ggPath.looseFocus() ;) } ggPath .setEnabled(state); ggChoose.setEnabled(state); if (ggType != null) ggType.setEnabled(state); if (ggRes != null) ggRes .setEnabled(state); if (ggRate != null) ggRate.setEnabled(state); feedback(); if (state) { ggPath.requestFocus(); } } public void addPathListener(PathListener list) { elm.addListener(list); } public void removePathListener(PathListener list) { elm.removeListener(list); } public void processEvent(BasicEvent e) { PathListener listener; for (int i = 0; i < elm.countListeners(); i++) { listener = (PathListener) elm.getListener(i); switch (e.getID()) { case PathEvent.CHANGED: listener.pathChanged((PathEvent) e); break; default: assert false : e.getID(); } } // for( i = 0; i < elm.countListeners(); i++ ) } public void handleTypes(int types[]) { handledTypes = types; if ((type & TYPE_BASICMASK) == TYPE_OUTPUTFILE) { ggType.removeAllItems(); for (int i = 0; i < types.length; i++) { ggType.addItem(GenericFile.getTypeDescr(types[i])); } } } /** * For Convenience a two-dimensional version */ public void handleTypes(int types[][]) { int i, j, num; int[] unitedTypes; for (i = 0, num = 0; i < types.length; i++) { num += types[i].length; } unitedTypes = new int[num]; for (i = 0, j = 0; i < types.length; i++) { for (num = 0; num < types[i].length; num++) { unitedTypes[j++] = types[i][num]; } } handleTypes(unitedTypes); } // -------- private methods -------- protected void calcFormat() { String fPath = getPath().getPath(); GenericFile f; int typ; String typeStr = null; boolean success = false; if ((fPath.length() > 0) && enabled) { try { f = new GenericFile(fPath, GenericFile.MODE_INPUT); typ = f.mode & GenericFile.MODE_TYPEMASK; typeStr = f.getTypeDescr(); for (int i = 0; i < handledTypes.length; i++) { if (typ == handledTypes[i]) { // setFormat( typeStr + "; " + f.getFormat() ); setFormat(f.getFormat()); success = true; break; } } if (!success) setFormat("Wrong format - " + typeStr); f.cleanUp(); } catch (IOException e1) { setFormat(((typeStr == null) ? "" : (typeStr + "; ")) + "I/O Error: " + e1.getMessage()); } } else { setFormat(""); success = true; } ggFormat.setPaint(success ? null : COLOR_ERR); } protected void checkExist() { String fPath = getPath().getPath(); boolean exists = false; Color c; if (fPath.length() > 0) { try { exists = new File(fPath).isFile(); } catch (SecurityException e) { // ignore } } c = exists && enabled ? COLOR_EXISTS : null; if (c != ggPath.getPaint()) { ggPath.setPaint(c); } } /* * Tags: $Dx = Directory of superPath x; $Fx = Filename; $E = Extension; $Bx = Brief filename */ protected String evalScheme(String schm) { String txt2; int i, j, k; for (i = schm.indexOf("$D"); (i >= 0) && (i < schm.length() - 2); i = schm.indexOf("$D", i)) { j = schm.charAt(i + 2) - 48; try { txt2 = superPaths[j].getPath().getPath(); } catch (ArrayIndexOutOfBoundsException e1) { txt2 = ""; } // sucky java 1.1 stringbuffer is impotent schm = schm.substring(0, i) + txt2.substring(0, txt2.lastIndexOf(File.separatorChar) + 1) + schm.substring(i + 3); } for (i = schm.indexOf("$F"); (i >= 0) && (i < schm.length() - 2); i = schm.indexOf("$F", i)) { j = schm.charAt(i + 2) - 48; try { txt2 = superPaths[j].getPath().getPath(); } catch (ArrayIndexOutOfBoundsException e1) { txt2 = ""; } txt2 = txt2.substring(txt2.lastIndexOf(File.separatorChar) + 1); k = txt2.lastIndexOf('.'); schm = schm.substring(0, i) + ((k > 0) ? txt2.substring(0, k) : txt2) + schm.substring(i + 3); } for (i = schm.indexOf("$X"); (i >= 0) && (i < schm.length() - 2); i = schm.indexOf("$X", i)) { j = schm.charAt(i + 2) - 48; try { txt2 = superPaths[j].getPath().getPath(); } catch (ArrayIndexOutOfBoundsException e1) { txt2 = ""; } txt2 = txt2.substring(txt2.lastIndexOf(File.separatorChar) + 1); k = txt2.lastIndexOf('.'); schm = schm.substring(0, i) + ((k > 0) ? txt2.substring(k) : "") + schm.substring(i + 3); } for (i = schm.indexOf("$B"); (i >= 0) && (i < schm.length() - 2); i = schm.indexOf("$B", i)) { j = schm.charAt(i + 2) - 48; try { txt2 = superPaths[j].getPath().getPath(); } catch (ArrayIndexOutOfBoundsException e1) { txt2 = ""; } txt2 = txt2.substring(txt2.lastIndexOf(File.separatorChar) + 1); k = txt2.lastIndexOf('.'); txt2 = abbreviate((k > 0) ? txt2.substring(0, k) : txt2); schm = schm.substring(0, i) + txt2 + schm.substring(i + 3); } for (i = schm.indexOf("$E"); i >= 0; i = schm.indexOf("$E", i)) { j = getType(); schm = schm.substring(0, i) + GenericFile.getExtStr(j) + schm.substring(i + 2); } return schm; } /** * A filename will be abbrevated. This is not so * critical on MacOS X any more because filenames * can be virtually as long as possible; on some * systems however when filenames are combinations * of more than one mother file this can be * crucial to keep the total filename length within * the file system's allowed bounds. */ protected static String abbreviate(String longStr) { StringBuffer shortStr; int i, j; char c; j = longStr.length(); if (j <= ABBR_LENGTH) return longStr; shortStr = new StringBuffer(j); for (i = 0; (i < j) && (shortStr.length() + j - i > ABBR_LENGTH); i++) { c = longStr.charAt(i); if (Character.isLetterOrDigit(c)) { shortStr.append(c); } } shortStr.append(longStr.substring(i)); longStr = shortStr.toString(); j = longStr.length(); if (j <= ABBR_LENGTH) return longStr; shortStr = new StringBuffer(j); shortStr.append(longStr.charAt(0)); for (i = 1; (i < j - 1) && (shortStr.length() + j - i > ABBR_LENGTH); i++) { c = longStr.charAt(i); if ("aeiouäöü".indexOf(c) < 0) { shortStr.append(c); } } shortStr.append(longStr.substring(i)); longStr = shortStr.toString(); j = longStr.length(); if (j <= ABBR_LENGTH) return longStr; i = (ABBR_LENGTH >> 1) - 1; return (longStr.substring(0, i) + '\'' + longStr.substring(longStr.length() - i)); } protected String createScheme(String applied) { String txt2; int i; int k = 0; int m; int checkedAbbrev; boolean checkedFull; if (applied.length() == 0) return protoScheme; for (i = 0; i < superPaths.length; i++) { txt2 = superPaths[i].getPath().getPath(); txt2 = txt2.substring(0, txt2.lastIndexOf(File.separatorChar) + 1); if (applied.startsWith(txt2)) { applied = "$D" + (char) (i + 48) + applied.substring(txt2.length()); k = 3; break; } } k = Math.max(k, applied.lastIndexOf(File.separatorChar) + 1); for (i = 0, checkedAbbrev = -1; i < superPaths.length; i++) { txt2 = superPaths[i].getPath().getPath(); txt2 = txt2.substring(txt2.lastIndexOf(File.separatorChar) + 1); m = txt2.lastIndexOf('.'); txt2 = (m > 0) ? txt2.substring(0, m) : txt2; if ((!protoScheme.contains("$B" + (char) (i + 48))) || (checkedAbbrev == i)) { m = applied.indexOf(txt2, k); if (m >= 0) { applied = applied.substring(0, m) + "$F" + (char) (i + 48) + applied.substring(m + txt2.length()); k = m + 3; continue; } checkedFull = true; } else { checkedFull = false; } if (checkedAbbrev == i) continue; txt2 = abbreviate(txt2); m = applied.indexOf(txt2, k); if (m >= 0) { applied = applied.substring(0, m) + "$B" + (char) (i + 48) + applied.substring(m + txt2.length()); k = m + 3; } else if (!checkedFull) { checkedAbbrev = i; i--; // retry non-abbrevated } } txt2 = GenericFile.getExtStr(getType()); if (applied.endsWith(txt2)) { applied = applied.substring(0, applied.length() - txt2.length()) + "$E"; } return applied; } protected String abbrScheme(String orig) { int i = orig.lastIndexOf("$F"); if (i >= 0) { return (orig.substring(0, i) + "$B" + orig.substring(i + 2)); } else { return orig; } } protected String expandScheme(String orig) { int i = orig.indexOf("$B"); if (i >= 0) { return (orig.substring(0, i) + "$F" + orig.substring(i + 2)); } else { return orig; } } protected String udirScheme(String orig, String udirPr) { int i; String udir = Application.userPrefs.get(udirPr, null); if (udir == null) return orig; if (orig.startsWith("$D")) { i = 3; } else { i = orig.lastIndexOf(File.separatorChar) + 1; } return (new File(udir, orig.substring(i)).getPath()); } protected void feedback() { if ((handledTypes != null) && ((type & TYPE_BASICMASK) == TYPE_INPUTFILE)) { calcFormat(); } else if ((type & TYPE_BASICMASK) == TYPE_OUTPUTFILE) { checkExist(); } } // -------- PathListener interface -------- // we're listening to ggChoose public void pathChanged(PathEvent e) { File path = e.getPath(); scheme = createScheme(path.getPath()); setPathAndDispatchEvent(path); } // -------- Action methods (ggPath Return-Hit) -------- // we're listening to ggPath public void actionPerformed(ActionEvent e) { String str = ggPath.getText(); if (str.length() == 0) { // automatic generation scheme = protoScheme; str = evalScheme(scheme); } else { scheme = createScheme(str); } setPathAndDispatchEvent(new File(str)); } // -------- Component methods (Panel) -------- public void componentResized(ComponentEvent e) { if ((ggType != null) && init) { // makes us update info on type, res, rate etc. init = false; itemStateChanged(new ItemEvent(ggType, ItemEvent.ITEM_STATE_CHANGED, ggType.getSelectedItem(), ItemEvent.SELECTED)); } } public void componentShown (ComponentEvent e) {} public void componentHidden(ComponentEvent e) {} public void componentMoved (ComponentEvent e) {} // -------- Item methods (ggType) -------- public void itemStateChanged(ItemEvent e) { int mode, ID, i; if (e.getSource() == ggType) { // -------------------- update Res/Rate fields --------- if ((ggRes != null) || (ggRate != null)) { mode = getType(); if (ggRes != null) { ID = ggRes.getSelectedIndex(); ggRes.removeAllItems(); if (Util.isValueInArray(mode, GenericFile.TYPES_SOUND)) { // --- sound ---- for (i = 0; i < sndResTxt.length; i++) { ggRes.addItem(sndResTxt[i]); } } else if (Util.isValueInArray(mode, GenericFile.TYPES_IMAGE)) { // --- image ---- for (i = 0; i < imgResTxt.length; i++) { ggRes.addItem(imgResTxt[i]); } } ggRes.setSelectedIndex(ID); } if (ggRate != null) { ID = ggRate.getSelectedIndex(); ggRate.removeAllItems(); if (Util.isValueInArray(mode, GenericFile.TYPES_SOUND)) { // --- sound ---- for (i = 0; i < sndRateTxt.length; i++) { ggRate.addItem(sndRateTxt[i]); } } else if (Util.isValueInArray(mode, GenericFile.TYPES_SPECT)) { // --- spect ---- for (i = 0; i < spectRateTxt.length; i++) { ggRate.addItem(spectRateTxt[i]); } } else if (Util.isValueInArray(mode, GenericFile.TYPES_MOVIE)) { // --- movie ---- for (i = 0; i < movRateTxt.length; i++) { ggRate.addItem(movRateTxt[i]); } } ggRate.setSelectedIndex(ID); } } setPathAndDispatchEvent(new File(evalScheme(scheme))); } } // -------- interne IOTextfeld-Klasse -------- class IOTextField extends ColouredTextField { public IOTextField() { super(32); InputMap inputMap = getInputMap(); ActionMap actionMap = getActionMap(); int i; String s; inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, InputEvent.META_MASK), "abbr"); actionMap.put("abbr", new AbstractAction() { public void actionPerformed(ActionEvent e) { scheme = abbrScheme(scheme); setPathAndDispatchEvent(new File(evalScheme(scheme))); } }); inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, InputEvent.META_MASK), "expd"); actionMap.put("expd", new AbstractAction() { public void actionPerformed(ActionEvent e) { scheme = expandScheme(scheme); setPathAndDispatchEvent(new File(evalScheme(scheme))); } }); inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, InputEvent.META_MASK), "auto"); actionMap.put("auto", new AbstractAction() { public void actionPerformed(ActionEvent e) { scheme = protoScheme; setPathAndDispatchEvent(new File(evalScheme(scheme))); } }); for (i = 1; i <= 9; i++) { s = "sudir" + i; inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_NUMPAD0 + i, InputEvent.META_MASK + InputEvent.SHIFT_MASK), s); actionMap.put(s, new SetUserDirAction(i)); s = "rudir" + i; inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_NUMPAD0 + i, InputEvent.META_MASK), s); actionMap.put(s, new RecallUserDirAction(i)); } } class SetUserDirAction extends AbstractAction { private int idx; private javax.swing.Timer visualFeedback; private Paint oldPaint = null; public SetUserDirAction(int idx) { this.idx = idx; visualFeedback = new javax.swing.Timer(250, this); visualFeedback.setRepeats(false); } public void actionPerformed(ActionEvent e) { if (e.getSource() == visualFeedback) { ggPath.setPaint(oldPaint); } else { String dir = getPath().getParent(); if (dir != null) { Application.userPrefs.put("UserDir" + idx, dir); if (visualFeedback.isRunning()) { visualFeedback.restart(); } else { oldPaint = ggPath.getPaint(); ggPath.setPaint(COLOR_PROPSET); visualFeedback.start(); } } } } } class RecallUserDirAction extends AbstractAction { private int idx; public RecallUserDirAction(int idx) { this.idx = idx; } public void actionPerformed(ActionEvent e) { scheme = udirScheme(scheme, "UserDir" + idx); setPathAndDispatchEvent(new File(evalScheme(scheme))); } } } // class IOTextField }