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