From 3ee6688534e5754b28d4ac8c6048b381fc14c012 Mon Sep 17 00:00:00 2001 From: Albert Cardona Date: Sat, 11 Apr 2009 19:28:54 +0200 Subject: [PATCH] Added automatic GUI to fix file paths. --- ini/trakem2/display/Patch.java | 11 +- ini/trakem2/persistence/FSLoader.java | 20 +++ ini/trakem2/persistence/FilePathRepair.java | 267 ++++++++++++++++++++++++++++ ini/trakem2/persistence/Loader.java | 7 +- ini/trakem2/utils/Dispatcher.java | 7 +- 5 files changed, 307 insertions(+), 5 deletions(-) create mode 100644 ini/trakem2/persistence/FilePathRepair.java diff --git a/ini/trakem2/display/Patch.java b/ini/trakem2/display/Patch.java index 00c2017a..f14a2bf8 100644 --- a/ini/trakem2/display/Patch.java +++ b/ini/trakem2/display/Patch.java @@ -891,7 +891,7 @@ public final class Patch extends Displayable { /** Expects x,y in world coordinates. This method is intended for grabing an occasional pixel; to grab all pixels, see @getImageProcessor method. */ public int[] getPixel(final int x, final int y, final double mag) { - if (1 == mag && project.getLoader().isUnloadable(this)) return new int[4]; + if (project.getLoader().isUnloadable(this)) return new int[4]; final Image img = project.getLoader().fetchImage(this, mag); if (Loader.isSignalImage(img)) return new int[4]; final int w = img.getWidth(null); @@ -945,6 +945,11 @@ public final class Patch extends Displayable { return project.getLoader().getAbsolutePath(this); } + /** Returns the absolute path to the image file, as read by the OS. */ + public final String getImageFilePath() { + return project.getLoader().getAbsoluteFilePath(this); + } + /** Returns the value of the field current_path, which may be null. If not null, the value may contain the slice info in it if it's part of a stack. */ public final String getCurrentPath() { return current_path; } @@ -1157,7 +1162,9 @@ public final class Patch extends Displayable { if (null != pi) return pi; // else, a new one with the untransformed, original image (a duplicate): project.getLoader().releaseToFit(o_width, o_height, type, 3); - return new PatchImage(getImageProcessor().duplicate(), project.getLoader().fetchImageMask(this), null, new Rectangle(0, 0, o_width, o_height), false); + final ImageProcessor ip = getImageProcessor(); + if (null == ip) return null; + return new PatchImage(ip.duplicate(), project.getLoader().fetchImageMask(this), null, new Rectangle(0, 0, o_width, o_height), false); } private boolean has_alpha = false; diff --git a/ini/trakem2/persistence/FSLoader.java b/ini/trakem2/persistence/FSLoader.java index a1636b96..4d55d8f5 100644 --- a/ini/trakem2/persistence/FSLoader.java +++ b/ini/trakem2/persistence/FSLoader.java @@ -556,6 +556,9 @@ public final class FSLoader extends Loader { Utils.log("FSLoader.fetchImagePlus: no image exists for patch " + p + " at path " + path); hs_unloadable.add(p); } + if (ControlWindow.isGUIEnabled()) { + FilePathRepair.add(p); + } removePatchLoadingLock(plock); unlock(); plock.unlock(); @@ -955,6 +958,14 @@ public final class FSLoader extends Loader { return path; } + public final String getAbsoluteFilePath(final Patch p) { + final String path = getAbsolutePath(p); + if (null == path) return null; + final int i = path.lastIndexOf("----#slice"); + return -1 == i ? path + : path.substring(0, i); + } + public static final boolean isURL(final String path) { return null != path && 0 == path.indexOf("http://"); } @@ -1556,6 +1567,10 @@ public final class FSLoader extends Loader { cannot_regenerate.add(patch); return false; } + if (hs_unloadable.contains(patch)) { + FilePathRepair.add(patch); + return false; + } synchronized (gm_lock) { try { gm_lock(); @@ -1600,6 +1615,11 @@ public final class FSLoader extends Loader { // Obtain an image which may be coordinate-transformed, and an alpha mask. Patch.PatchImage pai = patch.createTransformedImage(); + if (null == pai) { + Utils.log("Can't regenerate mipmaps for patch " + patch); + cannot_regenerate.add(patch); + return false; + } ip = pai.target; alpha_mask = pai.mask; // can be null outside_mask = pai.outside; // can be null diff --git a/ini/trakem2/persistence/FilePathRepair.java b/ini/trakem2/persistence/FilePathRepair.java new file mode 100644 index 00000000..54c8f35d --- /dev/null +++ b/ini/trakem2/persistence/FilePathRepair.java @@ -0,0 +1,267 @@ +package ini.trakem2.persistence; + +import java.io.File; +import java.util.Hashtable; +import java.util.HashSet; +import java.util.Vector; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; + +import javax.swing.BoxLayout; +import javax.swing.table.AbstractTableModel; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.border.LineBorder; +import javax.swing.JScrollPane; +import javax.swing.JTable; +import javax.swing.SwingUtilities; + +import ij.ImagePlus; +import ij.gui.GenericDialog; +import ij.io.OpenDialog; + +import ini.trakem2.ControlWindow; +import ini.trakem2.Project; +import ini.trakem2.display.Patch; +import ini.trakem2.persistence.Loader; +import ini.trakem2.utils.Dispatcher; +import ini.trakem2.utils.IJError; +import ini.trakem2.utils.Utils; + +/** A class to manage "file not found" problems. */ +public class FilePathRepair { + + private final Project project; + private final PathTableModel data = new PathTableModel(); + private final JTable table = new JTable(data); + private final JFrame frame; + + private FilePathRepair(final Project project) { + this.project = project; + this.frame = ControlWindow.createJFrame("Repair: " + project); + } + + private final Runnable makeGUI() { + return new Runnable() { public void run() { + JScrollPane jsp = new JScrollPane(table); + jsp.setPreferredSize(new Dimension(500, 500)); + table.addMouseListener(listener); + JLabel label = new JLabel("Double-click any to repair file path:"); + JLabel label2 = new JLabel("(Any listed with identical parent folder will be fixed as well.)"); + JPanel plabel = new JPanel(); + BoxLayout pbl = new BoxLayout(plabel, BoxLayout.Y_AXIS); + plabel.setLayout(pbl); + //plabel.setBorder(new LineBorder(Color.black, 1, true)); + plabel.setMinimumSize(new Dimension(400, 40)); + plabel.add(label); + plabel.add(label2); + JPanel all = new JPanel(); + BoxLayout bl = new BoxLayout(all, BoxLayout.Y_AXIS); + all.setLayout(bl); + all.add(plabel); + all.add(jsp); + frame.getContentPane().add(all); + frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); + frame.pack(); + ij.gui.GUI.center(frame); + frame.setVisible(true); + }}; + } + + private static class PathTableModel extends AbstractTableModel { + final Vector vp = new Vector(); + final Vector vpath = new Vector(); + final HashSet set = new HashSet(); + PathTableModel() {} + public final String getColumnName(final int col) { + switch (col) { + case 0: return "Image"; + case 1: return "Nonexistent file path"; + default: return ""; + } + } + public final int getRowCount() { return vp.size(); } + public final int getColumnCount() { return 2; } + public final Object getValueAt(final int row, final int col) { + switch (col) { + case 0: return vp.get(row); + case 1: return vpath.get(row); + default: return null; + } + } + public final boolean isCellEditable(final int row, final int col) { + return false; + } + public final void setValueAt(final Object value, final int row, final int col) {} // ignore + + synchronized public final void add(final Patch patch) { + if (set.contains(patch)) return; // already here + vp.add(patch); + final String path = patch.getFilePath(); + final int i = path.lastIndexOf("-----#slice="); + vpath.add(-1 == i ? path : path.substring(0, i)); + set.add(patch); + } + + synchronized public final String remove(final int row) { + set.remove(vp.remove(row)); + return vpath.remove(row); + } + + synchronized public final String remove(final Patch p) { + final int i = vp.indexOf(p); + if (-1 == i) return null; + set.remove(vp.remove(i)); + return vpath.remove(i); + } + } + + // Static part + + static private final Hashtable projects = new Hashtable(); + + static public void add(final Patch patch) { + dispatcher.exec(new Runnable() { public void run() { + final Project project = patch.getProject(); + FilePathRepair fpr = null; + synchronized (projects) { + fpr = projects.get(project); + if (null == fpr) { + fpr = new FilePathRepair(project); + projects.put(project, fpr); + SwingUtilities.invokeLater(fpr.makeGUI()); + } + fpr.data.add(patch); + if (!fpr.frame.isVisible()) { + fpr.frame.setVisible(true); + } else { + SwingUtilities.invokeLater(new Repainter(fpr)); + } + } + }}); + } + + static private class Repainter implements Runnable { + FilePathRepair fpr; + Repainter(final FilePathRepair fpr) { this.fpr = fpr; } + public void run() { + try { + fpr.table.updateUI(); + fpr.table.repaint(); + fpr.frame.pack(); + } catch (Exception e) { IJError.print(e); } + } + } + + static private final Dispatcher dispatcher = new Dispatcher("File path fixer"); + + static private MouseAdapter listener = new MouseAdapter() { + public void mousePressed(final MouseEvent me) { + final JTable table = (JTable) me.getSource(); + final PathTableModel data = (PathTableModel) table.getModel(); + final int row = table.rowAtPoint(me.getPoint()); + if (-1 == row) return; + + if (2 == me.getClickCount()) { + dispatcher.exec(new Runnable() { public void run() { + try { + table.setEnabled(false); + GenericDialog gd = new GenericDialog("Fix paths"); + gd.addCheckbox("Fix others with identical parent directory", true); + gd.addCheckbox("Update mipmaps for each fixed path", false); + gd.showDialog(); + if (!gd.wasCanceled()) { + fixPath(table, data, row, gd.getNextBoolean(), gd.getNextBoolean()); + } + } catch (Exception e) { + IJError.print(e); + } finally { + table.setEnabled(true); + } + }}); + } + } + }; + + static private void fixPath(final JTable table, final PathTableModel data, final int row, final boolean fix_similar, final boolean update_mipmaps) throws Exception { + synchronized (projects) { + final Patch patch = data.vp.get(row); + if (null == patch) return; + final String old_path = patch.getImageFilePath(); + final File f = new File(old_path); + if (f.exists()) { + Utils.log("File exists for " + patch + " at " + f.getAbsolutePath() + "\n --> not updating."); + data.remove(row); + return; + } + // Else, pop up file dialog + OpenDialog od = new OpenDialog("Select image file", OpenDialog.getDefaultDirectory(), null); + final String dir = od.getDirectory(); + final String filename = od.getFileName(); + if (null == dir) return; // dialog was canceled + final String path = new StringBuffer(dir).append(filename).toString(); // save concat, no backslash altered + if (!fixPatchPath(patch, path, update_mipmaps)) { + return; + } + // Remove from table + String wrong_parent_path = new File(data.remove(row)).getParent(); + if (!wrong_parent_path.endsWith(File.separator)) wrong_parent_path = new StringBuffer(wrong_parent_path).append(File.separatorChar).toString(); // backslash-safe + String good_parent_path = dir; + if (!dir.endsWith(File.separator)) good_parent_path = new StringBuffer(good_parent_path).append(File.separatorChar).toString(); // backslash-safe + // Check for similar parent paths and see if they can be fixed + if (fix_similar) { + for (int i=data.vp.size() -1; i>-1; i--) { + final String wrong_path = data.vpath.get(i); + final Patch p = data.vp.get(i); + if (wrong_path.startsWith(wrong_parent_path)) { + // try to fix as well + File file = new File(new StringBuffer(good_parent_path).append(wrong_path.substring(wrong_parent_path.length())).toString()); + if (file.exists()) { + if (fixPatchPath(p, file.getAbsolutePath(), update_mipmaps)) { + data.remove(p); // not by 'i' but by Patch, since if some fail the order is not the same + } + } + } + } + } + + // if table is empty, close + if (0 == data.vp.size()) { + FilePathRepair fpr = projects.remove(patch.getProject()); + fpr.frame.dispose(); + } + } + } + + static private boolean fixPatchPath(final Patch patch, final String new_path, final boolean update_mipmaps) { + try { + // Open the image header to check that dimensions match + final Loader loader = patch.getProject().getLoader(); + loader.releaseToFit(Math.max(Loader.MIN_FREE_BYTES, patch.getOWidth() * patch.getOHeight() * 10)); + final Dimension dim = loader.getDimensions(new_path); + if (null == dim) { + Utils.log(new StringBuffer("ERROR: could not open image at ").append(new_path).toString()); // preserving backslashes + return false; + } + // Check and set dimensions + if (dim.width != patch.getOWidth() || dim.height != patch.getOHeight()) { + Utils.log("ERROR different o_width,o_height for patch " + patch + "\n at new path " + new_path); + return false; + } + // flag as good + loader.removeFromUnloadable(patch); + // Assign new image path + loader.addedPatchFrom(new_path, patch); + // submit job to regenerate mipmaps in the background + if (update_mipmaps) loader.regenerateMipMaps(patch); + return true; + } catch (Exception e) { + IJError.print(e); + return false; + } + } +} diff --git a/ini/trakem2/persistence/Loader.java b/ini/trakem2/persistence/Loader.java index 33155670..a7aad4e4 100644 --- a/ini/trakem2/persistence/Loader.java +++ b/ini/trakem2/persistence/Loader.java @@ -185,7 +185,7 @@ abstract public class Loader { } - protected final HashSet hs_unloadable = new HashSet(); + protected final Set hs_unloadable = Collections.synchronizedSet(new HashSet()); static public final BufferedImage NOT_FOUND = new BufferedImage(10, 10, BufferedImage.TYPE_BYTE_INDEXED, Loader.GRAY_LUT); static { @@ -3730,6 +3730,9 @@ abstract public class Loader { /** Returns null unless overriden. This is intended for FSLoader projects. */ public String getAbsolutePath(final Patch patch) { return null; } + /** Returns null unless overriden. This is intended for FSLoader projects. */ + public String getAbsoluteFilePath(final Patch p) { return null; } + /** Does nothing unless overriden. */ public void setupMenuItems(final JMenu menu, final Project project) {} @@ -4982,6 +4985,8 @@ abstract public class Loader { public boolean isUnloadable(final Patch p) { return hs_unloadable.contains(p); } + public void removeFromUnloadable(final Patch p) { hs_unloadable.remove(p); } + protected static final BufferedImage createARGBImage(final int width, final int height, final int[] pix) { final BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); // In one step, set pixels that contain the alpha byte already: diff --git a/ini/trakem2/utils/Dispatcher.java b/ini/trakem2/utils/Dispatcher.java index b4116e5f..2512d6d2 100644 --- a/ini/trakem2/utils/Dispatcher.java +++ b/ini/trakem2/utils/Dispatcher.java @@ -38,12 +38,15 @@ public class Dispatcher extends Thread { private final Lock lock = new Lock(); private boolean go = true; - public Dispatcher() { - super("T2-Dispatcher"); + public Dispatcher(String tag) { + super("T2-Dispatcher" + (null != tag ? " " + tag : "")); setPriority(Thread.NORM_PRIORITY); setDaemon(true); start(); } + public Dispatcher() { + this(null); + } public void quit() { this.go = false; synchronized (this) { notify(); } -- 2.11.4.GIT