1 package ini
.trakem2
.persistence
;
4 import ij
.gui
.GenericDialog
;
5 import ij
.io
.OpenDialog
;
6 import ini
.trakem2
.ControlWindow
;
7 import ini
.trakem2
.Project
;
8 import ini
.trakem2
.display
.Displayable
;
9 import ini
.trakem2
.display
.Patch
;
10 import ini
.trakem2
.display
.YesNoDialog
;
11 import ini
.trakem2
.utils
.Dispatcher
;
12 import ini
.trakem2
.utils
.IJError
;
13 import ini
.trakem2
.utils
.Utils
;
15 import java
.awt
.Dimension
;
16 import java
.awt
.event
.MouseAdapter
;
17 import java
.awt
.event
.MouseEvent
;
18 import java
.awt
.event
.WindowAdapter
;
19 import java
.awt
.event
.WindowEvent
;
21 import java
.util
.Collection
;
22 import java
.util
.HashSet
;
23 import java
.util
.Hashtable
;
25 import java
.util
.Vector
;
27 import javax
.swing
.BoxLayout
;
28 import javax
.swing
.JFrame
;
29 import javax
.swing
.JLabel
;
30 import javax
.swing
.JPanel
;
31 import javax
.swing
.JScrollPane
;
32 import javax
.swing
.JTable
;
33 import javax
.swing
.SwingUtilities
;
34 import javax
.swing
.table
.AbstractTableModel
;
36 /** A class to manage "file not found" problems. */
37 public class FilePathRepair
{
39 private final Project project
;
40 private final PathTableModel data
= new PathTableModel();
41 private final JTable table
= new JTable(data
);
42 private final JFrame frame
;
44 private FilePathRepair(final Project project
) {
45 this.project
= project
;
46 this.frame
= ControlWindow
.createJFrame("Repair: " + project
);
49 private final Runnable
makeGUI() {
50 return new Runnable() { public void run() {
51 JScrollPane jsp
= new JScrollPane(table
);
52 jsp
.setPreferredSize(new Dimension(500, 500));
53 table
.addMouseListener(listener
);
54 JLabel label
= new JLabel("Double-click any to repair file path:");
55 JLabel label2
= new JLabel("(Any listed with identical parent folder will be fixed as well.)");
56 JPanel plabel
= new JPanel();
57 BoxLayout pbl
= new BoxLayout(plabel
, BoxLayout
.Y_AXIS
);
58 plabel
.setLayout(pbl
);
59 //plabel.setBorder(new LineBorder(Color.black, 1, true));
60 plabel
.setMinimumSize(new Dimension(400, 40));
63 JPanel all
= new JPanel();
64 BoxLayout bl
= new BoxLayout(all
, BoxLayout
.Y_AXIS
);
68 frame
.getContentPane().add(all
);
69 frame
.setDefaultCloseOperation(JFrame
.DISPOSE_ON_CLOSE
);
70 frame
.addWindowListener(new WindowAdapter() {
71 public void windowClosing(WindowEvent we
) {
72 synchronized (projects
) {
73 if (data
.vpath
.size() > 0 ) {
74 Utils
.logAll("WARNING: Some images remain associated to inexistent file paths.");
76 projects
.remove(project
);
81 ij
.gui
.GUI
.center(frame
);
82 frame
.setVisible(true);
86 private static class PathTableModel
extends AbstractTableModel
{
87 final Vector
<Patch
> vp
= new Vector
<Patch
>();
88 final Vector
<String
> vpath
= new Vector
<String
>();
89 final HashSet
<Patch
> set
= new HashSet
<Patch
>();
91 public final String
getColumnName(final int col
) {
93 case 0: return "Image";
94 case 1: return "Nonexistent file path";
98 public final int getRowCount() { return vp
.size(); }
99 public final int getColumnCount() { return 2; }
100 public final Object
getValueAt(final int row
, final int col
) {
102 case 0: return vp
.get(row
);
103 case 1: return vpath
.get(row
);
104 default: return null;
107 public final boolean isCellEditable(final int row
, final int col
) {
110 public final void setValueAt(final Object value
, final int row
, final int col
) {} // ignore
112 synchronized public final void add(final Patch patch
) {
113 if (set
.contains(patch
)) return; // already here
115 vpath
.add(patch
.getImageFilePath()); // no slice information if it's a stack
119 synchronized public final String
remove(final int row
) {
120 set
.remove(vp
.remove(row
));
121 return vpath
.remove(row
);
124 synchronized public final String
remove(final Patch p
) {
125 final int i
= vp
.indexOf(p
);
126 if (-1 == i
) return null;
127 set
.remove(vp
.remove(i
));
128 return vpath
.remove(i
);
134 static private final Hashtable
<Project
,FilePathRepair
> projects
= new Hashtable
<Project
,FilePathRepair
>();
136 static public void add(final Patch patch
) {
137 dispatcher
.exec(new Runnable() { public void run() {
138 final Project project
= patch
.getProject();
139 FilePathRepair fpr
= null;
140 synchronized (projects
) {
141 fpr
= projects
.get(project
);
143 fpr
= new FilePathRepair(project
);
144 projects
.put(project
, fpr
);
145 SwingUtilities
.invokeLater(fpr
.makeGUI());
148 if (!fpr
.frame
.isVisible()) {
149 fpr
.frame
.setVisible(true);
151 SwingUtilities
.invokeLater(new Repainter(fpr
));
157 static private class Repainter
implements Runnable
{
159 Repainter(final FilePathRepair fpr
) { this.fpr
= fpr
; }
162 fpr
.table
.updateUI();
165 } catch (Exception e
) { IJError
.print(e
); }
169 static private final Dispatcher dispatcher
= new Dispatcher("File path fixer");
171 static private MouseAdapter listener
= new MouseAdapter() {
172 public void mousePressed(final MouseEvent me
) {
173 final JTable table
= (JTable
) me
.getSource();
174 final PathTableModel data
= (PathTableModel
) table
.getModel();
175 final int row
= table
.rowAtPoint(me
.getPoint());
176 if (-1 == row
) return;
178 if (2 == me
.getClickCount()) {
179 dispatcher
.exec(new Runnable() { public void run() {
181 table
.setEnabled(false);
182 GenericDialog gd
= new GenericDialog("Fix paths");
183 gd
.addCheckbox("Fix other listed image files with identical parent directory", true);
184 gd
.addCheckbox("Fix all image files in the project with identical parent directory", true);
185 gd
.addCheckbox("Update mipmaps for each fixed path", false);
187 if (!gd
.wasCanceled()) {
188 fixPath(table
, data
, row
, gd
.getNextBoolean(), gd
.getNextBoolean(), gd
.getNextBoolean());
190 } catch (Exception e
) {
193 table
.setEnabled(true);
200 static private void fixPath(final JTable table
, final PathTableModel data
, final int row
, final boolean fix_similar
, final boolean fix_all
, final boolean update_mipmaps
) throws Exception
{
201 synchronized (projects
) {
202 final Patch patch
= data
.vp
.get(row
);
203 if (null == patch
) return;
204 final String old_path
= patch
.getImageFilePath();
205 final File f
= new File(old_path
);
207 Utils
.log("File exists for " + patch
+ " at " + f
.getAbsolutePath() + "\n --> not updating path.");
211 // Else, pop up file dialog
212 OpenDialog od
= new OpenDialog("Select image file", OpenDialog
.getDefaultDirectory(), null);
213 String dir
= od
.getDirectory();
214 final String filename
= od
.getFileName();
215 if (null == dir
) return; // dialog was canceled
216 if (IJ
.isWindows()) dir
= dir
.replace('\\', '/');
217 if (!dir
.endsWith("/")) dir
+= "/";
219 if ( ! filename
.equals(f
.getName())) {
220 YesNoDialog yn
= new YesNoDialog(projects
.get(patch
.getProject()).frame
, "WARNING", "Different file names!\n old: " + f
.getName() + "\n new: " + filename
+ "\nSet to new file name?");
221 if ( ! yn
.yesPressed()) return;
223 // Remove mipmaps: would not be found with the new name and the old ones would remain behind unused
224 if ( ! f
.getName().equals(new File(old_path
).getName())) {
225 // remove mipmaps: the name wouldn't match otherwise
226 patch
.getProject().getLoader().removeMipMaps(patch
);
230 String wrong_parent_path
= new File(data
.vpath
.get(row
)).getParent();
231 wrong_parent_path
= wrong_parent_path
.replace('\\', '/');
232 if (!wrong_parent_path
.endsWith("/")) wrong_parent_path
= new StringBuilder(wrong_parent_path
).append('/').toString(); // not File.separatorChar, TrakEM2 uses '/' as folder separator
234 final String path
= new StringBuilder(dir
).append(filename
).toString();
236 // keep track of fixed slices to avoid calling n_slices * n_slices times!
237 final HashSet
<Patch
> fixed
= new HashSet
<Patch
>();
241 if (-1 == patch
.getFilePath().lastIndexOf("-----#slice=")) {
242 if (!fixPatchPath(patch
, path
, update_mipmaps
)) {
249 int n
= fixStack(data
, fixed
, patch
.getStackPatches(), old_path
, path
, update_mipmaps
);
251 return; // some error ocurred, no paths fixed
254 // data already cleared of removed patches by fixStack
257 String good_parent_path
= dir
;
258 if (!dir
.endsWith("/")) good_parent_path
= new StringBuilder(good_parent_path
).append('/').toString(); // not File.separatorChar, TrakEM2 uses '/' as folder separator
260 // Check for similar parent paths and see if they can be fixed
262 for (int i
=data
.vp
.size() -1; i
>-1; i
--) {
263 final String wrong_path
= data
.vpath
.get(i
);
264 final Patch p
= data
.vp
.get(i
);
265 if (wrong_path
.startsWith(wrong_parent_path
)) {
266 // try to fix as well
267 final File file
= new File(new StringBuilder(good_parent_path
).append(wrong_path
.substring(wrong_parent_path
.length())).toString());
269 if (-1 == p
.getFilePath().lastIndexOf("-----#slice=")) {
270 if (!fixed
.contains(p
) && fixPatchPath(p
, file
.getAbsolutePath(), update_mipmaps
)) {
271 data
.remove(p
); // not by 'i' but by Patch, since if some fail the order is not the same
276 if (fixed
.contains(p
)) continue;
277 n_fixed
+= fixStack(data
, fixed
, p
.getStackPatches(), wrong_path
, file
.getAbsolutePath(), update_mipmaps
);
284 // traverse all Patch from the entire project, minus those already fixed
285 for (final Displayable d
: patch
.getLayerSet().getDisplayables(Patch
.class)) {
286 final Patch p
= (Patch
) d
;
287 final String wrong_path
= p
.getImageFilePath();
288 if (wrong_path
.startsWith(wrong_parent_path
)) {
289 File file
= new File(new StringBuilder(good_parent_path
).append(wrong_path
.substring(wrong_parent_path
.length())).toString());
291 if (-1 == p
.getFilePath().lastIndexOf("-----#slice=")) {
292 if (!fixed
.contains(p
) && fixPatchPath(p
, file
.getAbsolutePath(), update_mipmaps
)) {
293 data
.remove(p
); // not by 'i' but by Patch, since if some fail the order is not the same
298 if (fixed
.contains(p
)) continue;
299 n_fixed
+= fixStack(data
, fixed
, p
.getStackPatches(), wrong_path
, file
.getAbsolutePath(), update_mipmaps
);
306 // if table is empty, close
307 if (0 == data
.vp
.size()) {
308 FilePathRepair fpr
= projects
.remove(patch
.getProject());
312 Utils
.logAll("Fixed " + n_fixed
+ " image file path" + (n_fixed
> 1 ?
"s" : ""));
316 static private int fixStack(final PathTableModel data
, final Set
<Patch
> fixed
, final Collection
<Patch
> slices
, final String wrong_path
, final String new_path
, final boolean update_mipmaps
) {
318 Dimension dim
= null;
319 Loader loader
= null;
320 for (final Patch ps
: slices
) {
321 if (fixed
.contains(ps
)) continue;
322 final String slicepath
= ps
.getFilePath();
323 final int isl
= slicepath
.lastIndexOf("-----#slice=");
325 Utils
.log2("Not a stack path: " + slicepath
);
326 continue; // someone linked an image...
328 final String ps_path
= slicepath
.substring(0, isl
); // same: // ps.getImageFilePath();
329 if (! ps_path
.substring(0, isl
).equals(wrong_path
)) {
330 Utils
.log2("Not the same stack path:\n i=" + ps_path
+ "\n ref=" + wrong_path
);
331 continue; // not the same stack!
334 loader
= ps
.getProject().getLoader();
335 loader
.releaseToFit(Math
.max(Loader
.MIN_FREE_BYTES
, ps
.getOWidth() * ps
.getOHeight() * 10));
336 dim
= loader
.getDimensions(new_path
);
338 Utils
.log(new StringBuilder("ERROR: could not open image at ").append(new_path
).toString()); // preserving backslashes
342 if (dim
.width
!= ps
.getOWidth() || dim
.height
!= ps
.getOHeight()) {
343 Utils
.log("ERROR different o_width,o_height for patch " + ps
+ "\n at new path " + new_path
+
344 "\nold o_width,o_height: " + ps
.getOWidth() + "," + ps
.getOHeight() +
345 "\nnew o_width,o_height: " + dim
.width
+ "," + dim
.height
);
351 loader
.removeFromUnloadable(ps
);
352 // Assign new image path with slice info appended
353 loader
.addedPatchFrom(new_path
+ slicepath
.substring(isl
), ps
);
354 // submit job to regenerate mipmaps in the background
355 if (update_mipmaps
) loader
.regenerateMipMaps(ps
);
363 static private boolean fixPatchPath(final Patch patch
, final String new_path
, final boolean update_mipmaps
) {
365 // Open the image header to check that dimensions match
366 final Loader loader
= patch
.getProject().getLoader();
367 loader
.releaseToFit(Math
.max(Loader
.MIN_FREE_BYTES
, patch
.getOWidth() * patch
.getOHeight() * 10));
368 final Dimension dim
= loader
.getDimensions(new_path
);
370 Utils
.log(new StringBuilder("ERROR: could not open image at ").append(new_path
).toString()); // preserving backslashes
373 // Check and set dimensions
374 if (dim
.width
!= patch
.getOWidth() || dim
.height
!= patch
.getOHeight()) {
375 Utils
.log("ERROR different o_width,o_height for patch " + patch
+ "\n at new path " + new_path
+
376 "\nold o_width,o_height: " + patch
.getOWidth() + "," + patch
.getOHeight() +
377 "\nnew o_width,o_height: " + dim
.width
+ "," + dim
.height
);
381 loader
.removeFromUnloadable(patch
);
382 // Assign new image path
383 loader
.addedPatchFrom(new_path
, patch
);
384 // submit job to regenerate mipmaps in the background
385 if (update_mipmaps
) loader
.regenerateMipMaps(patch
);
387 } catch (Exception e
) {