Added automatic GUI to fix file paths.
[trakem2.git] / ini / trakem2 / persistence / FilePathRepair.java
blob54c8f35d5978f37d1c82dd61e1a0c7c283a65c60
1 package ini.trakem2.persistence;
3 import java.io.File;
4 import java.util.Hashtable;
5 import java.util.HashSet;
6 import java.util.Vector;
8 import java.awt.Color;
9 import java.awt.Dimension;
10 import java.awt.event.MouseAdapter;
11 import java.awt.event.MouseEvent;
13 import javax.swing.BoxLayout;
14 import javax.swing.table.AbstractTableModel;
15 import javax.swing.JFrame;
16 import javax.swing.JLabel;
17 import javax.swing.JPanel;
18 import javax.swing.border.LineBorder;
19 import javax.swing.JScrollPane;
20 import javax.swing.JTable;
21 import javax.swing.SwingUtilities;
23 import ij.ImagePlus;
24 import ij.gui.GenericDialog;
25 import ij.io.OpenDialog;
27 import ini.trakem2.ControlWindow;
28 import ini.trakem2.Project;
29 import ini.trakem2.display.Patch;
30 import ini.trakem2.persistence.Loader;
31 import ini.trakem2.utils.Dispatcher;
32 import ini.trakem2.utils.IJError;
33 import ini.trakem2.utils.Utils;
35 /** A class to manage "file not found" problems. */
36 public class FilePathRepair {
38 private final Project project;
39 private final PathTableModel data = new PathTableModel();
40 private final JTable table = new JTable(data);
41 private final JFrame frame;
43 private FilePathRepair(final Project project) {
44 this.project = project;
45 this.frame = ControlWindow.createJFrame("Repair: " + project);
48 private final Runnable makeGUI() {
49 return new Runnable() { public void run() {
50 JScrollPane jsp = new JScrollPane(table);
51 jsp.setPreferredSize(new Dimension(500, 500));
52 table.addMouseListener(listener);
53 JLabel label = new JLabel("Double-click any to repair file path:");
54 JLabel label2 = new JLabel("(Any listed with identical parent folder will be fixed as well.)");
55 JPanel plabel = new JPanel();
56 BoxLayout pbl = new BoxLayout(plabel, BoxLayout.Y_AXIS);
57 plabel.setLayout(pbl);
58 //plabel.setBorder(new LineBorder(Color.black, 1, true));
59 plabel.setMinimumSize(new Dimension(400, 40));
60 plabel.add(label);
61 plabel.add(label2);
62 JPanel all = new JPanel();
63 BoxLayout bl = new BoxLayout(all, BoxLayout.Y_AXIS);
64 all.setLayout(bl);
65 all.add(plabel);
66 all.add(jsp);
67 frame.getContentPane().add(all);
68 frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
69 frame.pack();
70 ij.gui.GUI.center(frame);
71 frame.setVisible(true);
72 }};
75 private static class PathTableModel extends AbstractTableModel {
76 final Vector<Patch> vp = new Vector<Patch>();
77 final Vector<String> vpath = new Vector<String>();
78 final HashSet<Patch> set = new HashSet<Patch>();
79 PathTableModel() {}
80 public final String getColumnName(final int col) {
81 switch (col) {
82 case 0: return "Image";
83 case 1: return "Nonexistent file path";
84 default: return "";
87 public final int getRowCount() { return vp.size(); }
88 public final int getColumnCount() { return 2; }
89 public final Object getValueAt(final int row, final int col) {
90 switch (col) {
91 case 0: return vp.get(row);
92 case 1: return vpath.get(row);
93 default: return null;
96 public final boolean isCellEditable(final int row, final int col) {
97 return false;
99 public final void setValueAt(final Object value, final int row, final int col) {} // ignore
101 synchronized public final void add(final Patch patch) {
102 if (set.contains(patch)) return; // already here
103 vp.add(patch);
104 final String path = patch.getFilePath();
105 final int i = path.lastIndexOf("-----#slice=");
106 vpath.add(-1 == i ? path : path.substring(0, i));
107 set.add(patch);
110 synchronized public final String remove(final int row) {
111 set.remove(vp.remove(row));
112 return vpath.remove(row);
115 synchronized public final String remove(final Patch p) {
116 final int i = vp.indexOf(p);
117 if (-1 == i) return null;
118 set.remove(vp.remove(i));
119 return vpath.remove(i);
123 // Static part
125 static private final Hashtable<Project,FilePathRepair> projects = new Hashtable<Project,FilePathRepair>();
127 static public void add(final Patch patch) {
128 dispatcher.exec(new Runnable() { public void run() {
129 final Project project = patch.getProject();
130 FilePathRepair fpr = null;
131 synchronized (projects) {
132 fpr = projects.get(project);
133 if (null == fpr) {
134 fpr = new FilePathRepair(project);
135 projects.put(project, fpr);
136 SwingUtilities.invokeLater(fpr.makeGUI());
138 fpr.data.add(patch);
139 if (!fpr.frame.isVisible()) {
140 fpr.frame.setVisible(true);
141 } else {
142 SwingUtilities.invokeLater(new Repainter(fpr));
145 }});
148 static private class Repainter implements Runnable {
149 FilePathRepair fpr;
150 Repainter(final FilePathRepair fpr) { this.fpr = fpr; }
151 public void run() {
152 try {
153 fpr.table.updateUI();
154 fpr.table.repaint();
155 fpr.frame.pack();
156 } catch (Exception e) { IJError.print(e); }
160 static private final Dispatcher dispatcher = new Dispatcher("File path fixer");
162 static private MouseAdapter listener = new MouseAdapter() {
163 public void mousePressed(final MouseEvent me) {
164 final JTable table = (JTable) me.getSource();
165 final PathTableModel data = (PathTableModel) table.getModel();
166 final int row = table.rowAtPoint(me.getPoint());
167 if (-1 == row) return;
169 if (2 == me.getClickCount()) {
170 dispatcher.exec(new Runnable() { public void run() {
171 try {
172 table.setEnabled(false);
173 GenericDialog gd = new GenericDialog("Fix paths");
174 gd.addCheckbox("Fix others with identical parent directory", true);
175 gd.addCheckbox("Update mipmaps for each fixed path", false);
176 gd.showDialog();
177 if (!gd.wasCanceled()) {
178 fixPath(table, data, row, gd.getNextBoolean(), gd.getNextBoolean());
180 } catch (Exception e) {
181 IJError.print(e);
182 } finally {
183 table.setEnabled(true);
185 }});
190 static private void fixPath(final JTable table, final PathTableModel data, final int row, final boolean fix_similar, final boolean update_mipmaps) throws Exception {
191 synchronized (projects) {
192 final Patch patch = data.vp.get(row);
193 if (null == patch) return;
194 final String old_path = patch.getImageFilePath();
195 final File f = new File(old_path);
196 if (f.exists()) {
197 Utils.log("File exists for " + patch + " at " + f.getAbsolutePath() + "\n --> not updating.");
198 data.remove(row);
199 return;
201 // Else, pop up file dialog
202 OpenDialog od = new OpenDialog("Select image file", OpenDialog.getDefaultDirectory(), null);
203 final String dir = od.getDirectory();
204 final String filename = od.getFileName();
205 if (null == dir) return; // dialog was canceled
206 final String path = new StringBuffer(dir).append(filename).toString(); // save concat, no backslash altered
207 if (!fixPatchPath(patch, path, update_mipmaps)) {
208 return;
210 // Remove from table
211 String wrong_parent_path = new File(data.remove(row)).getParent();
212 if (!wrong_parent_path.endsWith(File.separator)) wrong_parent_path = new StringBuffer(wrong_parent_path).append(File.separatorChar).toString(); // backslash-safe
213 String good_parent_path = dir;
214 if (!dir.endsWith(File.separator)) good_parent_path = new StringBuffer(good_parent_path).append(File.separatorChar).toString(); // backslash-safe
215 // Check for similar parent paths and see if they can be fixed
216 if (fix_similar) {
217 for (int i=data.vp.size() -1; i>-1; i--) {
218 final String wrong_path = data.vpath.get(i);
219 final Patch p = data.vp.get(i);
220 if (wrong_path.startsWith(wrong_parent_path)) {
221 // try to fix as well
222 File file = new File(new StringBuffer(good_parent_path).append(wrong_path.substring(wrong_parent_path.length())).toString());
223 if (file.exists()) {
224 if (fixPatchPath(p, file.getAbsolutePath(), update_mipmaps)) {
225 data.remove(p); // not by 'i' but by Patch, since if some fail the order is not the same
232 // if table is empty, close
233 if (0 == data.vp.size()) {
234 FilePathRepair fpr = projects.remove(patch.getProject());
235 fpr.frame.dispose();
240 static private boolean fixPatchPath(final Patch patch, final String new_path, final boolean update_mipmaps) {
241 try {
242 // Open the image header to check that dimensions match
243 final Loader loader = patch.getProject().getLoader();
244 loader.releaseToFit(Math.max(Loader.MIN_FREE_BYTES, patch.getOWidth() * patch.getOHeight() * 10));
245 final Dimension dim = loader.getDimensions(new_path);
246 if (null == dim) {
247 Utils.log(new StringBuffer("ERROR: could not open image at ").append(new_path).toString()); // preserving backslashes
248 return false;
250 // Check and set dimensions
251 if (dim.width != patch.getOWidth() || dim.height != patch.getOHeight()) {
252 Utils.log("ERROR different o_width,o_height for patch " + patch + "\n at new path " + new_path);
253 return false;
255 // flag as good
256 loader.removeFromUnloadable(patch);
257 // Assign new image path
258 loader.addedPatchFrom(new_path, patch);
259 // submit job to regenerate mipmaps in the background
260 if (update_mipmaps) loader.regenerateMipMaps(patch);
261 return true;
262 } catch (Exception e) {
263 IJError.print(e);
264 return false;