diff --git a/source/net/sourceforge/filebot/ui/rename/RenameList.java b/source/net/sourceforge/filebot/ui/rename/RenameList.java index 79213ee8..f65b096e 100644 --- a/source/net/sourceforge/filebot/ui/rename/RenameList.java +++ b/source/net/sourceforge/filebot/ui/rename/RenameList.java @@ -32,8 +32,8 @@ class RenameList extends FileBotList { // replace default model with given model setModel(model); + list.setFixedCellHeight(28); // need a fixed cell high for high performance scrolling list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); - list.setFixedCellHeight(28); list.addMouseListener(dndReorderMouseAdapter); list.addMouseMotionListener(dndReorderMouseAdapter); diff --git a/source/net/sourceforge/filebot/ui/rename/RenameListCellRenderer.java b/source/net/sourceforge/filebot/ui/rename/RenameListCellRenderer.java index 50aaba7b..254e18bc 100644 --- a/source/net/sourceforge/filebot/ui/rename/RenameListCellRenderer.java +++ b/source/net/sourceforge/filebot/ui/rename/RenameListCellRenderer.java @@ -3,6 +3,7 @@ package net.sourceforge.filebot.ui.rename; import static net.sourceforge.filebot.similarity.EpisodeMetrics.*; +import static net.sourceforge.tuned.FileUtilities.*; import static net.sourceforge.tuned.ui.TunedUtilities.*; import java.awt.AlphaComposite; @@ -14,6 +15,7 @@ import java.awt.RenderingHints; import java.awt.geom.Rectangle2D; import java.awt.geom.RoundRectangle2D; import java.io.File; +import java.util.List; import javax.swing.DefaultListCellRenderer; import javax.swing.JList; @@ -35,15 +37,18 @@ import net.sourceforge.tuned.ui.GradientStyle; class RenameListCellRenderer extends DefaultFancyListCellRenderer { - private final RenameModel renameModel; + private RenameModel renameModel; - private final TypeRenderer typeRenderer = new TypeRenderer(); + private TypeRenderer typeRenderer = new TypeRenderer(); - private final Color noMatchGradientBeginColor = new Color(0xB7B7B7); - private final Color noMatchGradientEndColor = new Color(0x9A9A9A); + private Color noMatchGradientBeginColor = new Color(0xB7B7B7); + private Color noMatchGradientEndColor = new Color(0x9A9A9A); - private final Color warningGradientBeginColor = Color.RED; - private final Color warningGradientEndColor = new Color(0xDC143C); + private Color warningGradientBeginColor = Color.RED; + private Color warningGradientEndColor = new Color(0xDC143C); + + private Color pathRainbowBeginColor = new Color(0xFF7F50); + private Color pathRainbowEndColor = new Color(0x008080); public RenameListCellRenderer(RenameModel renameModel) { @@ -89,19 +94,26 @@ class RenameListCellRenderer extends DefaultFancyListCellRenderer { if (renameModel.preserveExtension()) { setText(FileUtilities.getName(file)); } else { - setText(file.getAbsolutePath()); + setText(isSelected || !renameModel.hasComplement(index) ? formatPath(file) : colorizePath(file.getAbsoluteFile(), true)); } } else if (value instanceof FormattedFuture) { // display progress icon FormattedFuture formattedFuture = (FormattedFuture) value; + float matchProbablity = renameModel.hasComplement(index) ? getMatchProbablity(formattedFuture.getMatch()) : 1; - if (!renameModel.preserveExtension() && formattedFuture.isDone() && renameModel.hasComplement(index)) { - // absolute path mode - File targetDir = renameModel.getMatch(index).getCandidate().getParentFile(); - setText(resolveAbsolutePath(targetDir, formattedFuture.toString())); + if (formattedFuture.isDone() && !formattedFuture.isCancelled()) { + if (!renameModel.preserveExtension() && renameModel.hasComplement(index)) { + // absolute path mode + File targetDir = renameModel.getMatch(index).getCandidate().getParentFile(); + File path = resolveAbsolutePath(targetDir, formattedFuture.toString()); + setText(isSelected || matchProbablity < 1 ? formatPath(path) : colorizePath(path, true)); + } else { + // relative name mode + File path = new File(formattedFuture.toString()); + setText(isSelected || matchProbablity < 1 ? formatPath(path) : colorizePath(path, !renameModel.preserveExtension())); + } } else { - // relative name mode - setText(formattedFuture.isDone() && !formattedFuture.isCancelled() ? formattedFuture.toString() : formattedFuture.preview()); + setText(formattedFuture.preview()); // default text } switch (formattedFuture.getState()) { @@ -114,7 +126,6 @@ class RenameListCellRenderer extends DefaultFancyListCellRenderer { } if (renameModel.hasComplement(index)) { - float matchProbablity = getMatchProbablity(formattedFuture.getMatch()); setOpaque(true); // enable paint background setBackground(derive(warningGradientBeginColor, (1 - matchProbablity) * 0.5f)); // alpha indicates match probability @@ -136,16 +147,48 @@ class RenameListCellRenderer extends DefaultFancyListCellRenderer { } - protected String resolveAbsolutePath(File targetDir, String path) { + protected String formatPath(File file) { + return normalizePathSeparators(file.getPath()); + } + + + protected String colorizePath(File file, boolean hasExtension) { + List path = listPath(file); + StringBuilder html = new StringBuilder(512); + html.append(""); + + // colorize parent path + for (int i = 0; i < path.size() - 1; i++) { + float f = (path.size() <= 2) ? 1 : (float) i / (path.size() - 2); + Color c = interpolateHSB(pathRainbowBeginColor, pathRainbowEndColor, f); + html.append(String.format("%4$s/", c.getRed(), c.getGreen(), c.getBlue(), escapeHTML(FileUtilities.getName(path.get(i))))); + } + + // only colorize extension + if (hasExtension) { + html.append(escapeHTML(FileUtilities.getName(file))); + String extension = FileUtilities.getExtension(file); + if (extension != null) { + html.append(String.format(".%s", escapeHTML(extension))); // highlight extension + } + } else { + html.append(file.getName()); + } + + return html.append("").toString(); + } + + + protected File resolveAbsolutePath(File targetDir, String path) { File f = new File(path); if (!f.isAbsolute()) { f = new File(targetDir, path); // resolve path against target folder } try { - return f.getCanonicalPath(); + return f.getCanonicalFile(); } catch (Exception e) { - return f.getAbsolutePath(); + return f.getAbsoluteFile(); } } diff --git a/source/net/sourceforge/tuned/FileUtilities.java b/source/net/sourceforge/tuned/FileUtilities.java index 9278a5f8..421c9e8d 100644 --- a/source/net/sourceforge/tuned/FileUtilities.java +++ b/source/net/sourceforge/tuned/FileUtilities.java @@ -261,7 +261,7 @@ public final class FileUtilities { return name; // file might be a drive (only has a path, but no name) - return file.toString(); + return replacePathSeparators(file.toString(), ""); } diff --git a/source/net/sourceforge/tuned/ui/TunedUtilities.java b/source/net/sourceforge/tuned/ui/TunedUtilities.java index eb4d36c7..1117a454 100644 --- a/source/net/sourceforge/tuned/ui/TunedUtilities.java +++ b/source/net/sourceforge/tuned/ui/TunedUtilities.java @@ -43,29 +43,51 @@ public final class TunedUtilities { public static final Color TRANSLUCENT = new Color(255, 255, 255, 0); - + public static void checkEventDispatchThread() { if (!SwingUtilities.isEventDispatchThread()) { throw new IllegalStateException("Method must be accessed from the Swing Event Dispatch Thread, but was called on Thread \"" + Thread.currentThread().getName() + "\""); } } - + + public static Color interpolateHSB(Color c1, Color c2, float f) { + float[] hsb1 = Color.RGBtoHSB(c1.getRed(), c1.getGreen(), c1.getBlue(), null); + float[] hsb2 = Color.RGBtoHSB(c2.getRed(), c2.getGreen(), c2.getBlue(), null); + float[] hsb = new float[3]; + + for (int i = 0; i < hsb.length; i++) { + hsb[i] = hsb1[i] + ((hsb2[i] - hsb1[i]) * f); + } + + return Color.getHSBColor(hsb[0], hsb[1], hsb[2]); + } + + + public static String escapeHTML(String s) { + char[] sc = new char[] { '&', '<', '>', '"', '\'' }; + for (char c : sc) { + s = s.replace(Character.toString(c), String.format("&#%d;", (int) c)); // e.g. & + } + return s; + } + + public static Color derive(Color color, float alpha) { return new Color(((int) ((alpha * 255)) << 24) | (color.getRGB() & 0x00FFFFFF), true); } - + public static boolean isShiftDown(ActionEvent evt) { return checkModifiers(evt.getModifiers(), ActionEvent.SHIFT_MASK); } - + public static boolean checkModifiers(int modifiers, int mask) { return ((modifiers & mask) == mask); } - + public static JButton createImageButton(Action action) { JButton button = new JButton(action); button.setHideActionText(true); @@ -74,7 +96,7 @@ public final class TunedUtilities { return button; } - + public static void installAction(JComponent component, KeyStroke keystroke, Action action) { Object key = action.getValue(Action.NAME); @@ -85,7 +107,7 @@ public final class TunedUtilities { component.getActionMap().put(key, action); } - + public static UndoManager installUndoSupport(JTextComponent component) { final UndoManager undoSupport = new UndoManager(); @@ -115,12 +137,12 @@ public final class TunedUtilities { return undoSupport; } - + public static boolean isMaximized(Frame frame) { return (frame.getExtendedState() & Frame.MAXIMIZED_BOTH) != 0; } - + public static List showMultiValueInputDialog(final String text, final String initialValue, final String title, final Component parent) throws InvocationTargetException, InterruptedException { String input = showInputDialog(text, initialValue, title, parent); if (input == null || input.isEmpty()) { @@ -145,7 +167,7 @@ public final class TunedUtilities { return singletonList(input); } - + public static String showInputDialog(final String text, final String initialValue, final String title, final Component parent) throws InvocationTargetException, InterruptedException { final StringBuilder buffer = new StringBuilder(); SwingUtilities.invokeAndWait(new Runnable() { @@ -163,7 +185,7 @@ public final class TunedUtilities { return buffer.length() == 0 ? null : buffer.toString(); } - + public static Window getWindow(Object component) { if (component instanceof Window) return (Window) component; @@ -174,7 +196,7 @@ public final class TunedUtilities { return null; } - + public static Point getOffsetLocation(Window owner) { if (owner == null) { Window[] toplevel = Window.getOwnerlessWindows(); @@ -192,7 +214,7 @@ public final class TunedUtilities { return new Point(p.x + d.width / 4, p.y + d.height / 7); } - + public static Image getImage(Icon icon) { if (icon == null) return null; @@ -210,12 +232,12 @@ public final class TunedUtilities { return image; } - + public static Dimension getDimension(Icon icon) { return new Dimension(icon.getIconWidth(), icon.getIconHeight()); } - + public static Timer invokeLater(int delay, final Runnable runnable) { Timer timer = new Timer(delay, new ActionListener() { @@ -231,7 +253,7 @@ public final class TunedUtilities { return timer; } - + /** * When trying to drag a row of a multi-select JTable, it will start selecting rows instead * of initiating a drag. This TableUI will give the JTable proper dnd behaviour. @@ -243,7 +265,7 @@ public final class TunedUtilities { return new DragDropRowMouseInputHandler(); } - + protected class DragDropRowMouseInputHandler extends MouseInputHandler { @Override @@ -258,7 +280,7 @@ public final class TunedUtilities { } } - + /** * Dummy constructor to prevent instantiation. */