package net.filebot.ui.list;
import static java.awt.Font.*;
import static java.util.stream.Collectors.*;
import static javax.swing.BorderFactory.*;
import static net.filebot.util.ui.SwingUI.*;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Font;
import java.awt.Toolkit;
import java.awt.datatransfer.StringSelection;
import java.awt.datatransfer.Transferable;
import java.util.List;
import java.util.ListIterator;
import java.util.stream.IntStream;
import javax.script.ScriptException;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JMenu;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JSpinner;
import javax.swing.JSpinner.NumberEditor;
import javax.swing.JTextField;
import javax.swing.SpinnerNumberModel;
import javax.swing.TransferHandler;
import org.fife.ui.rsyntaxtextarea.RSyntaxDocument;
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
import org.fife.ui.rsyntaxtextarea.SyntaxConstants;
import org.fife.ui.rtextarea.RTextScrollPane;
import com.google.common.eventbus.Subscribe;
import net.filebot.ResourceManager;
import net.filebot.format.ExpressionFormat;
import net.filebot.ui.FileBotList;
import net.filebot.ui.FileBotListExportHandler;
import net.filebot.ui.PanelBuilder;
import net.filebot.ui.transfer.LoadAction;
import net.filebot.ui.transfer.SaveAction;
import net.filebot.ui.transfer.TransferablePolicy;
import net.filebot.ui.transfer.TransferablePolicy.TransferAction;
import net.filebot.util.ui.DefaultFancyListCellRenderer;
import net.filebot.util.ui.LazyDocumentListener;
import net.filebot.util.ui.PrototypeCellValueUpdater;
import net.filebot.util.ui.SwingEventBus;
import net.miginfocom.swing.MigLayout;
public class ListPanel extends JComponent {
public static final String DEFAULT_SEQUENCE_FORMAT = "Sequence - {i.pad(2)}";
public static final String DEFAULT_FILE_FORMAT = "{fn}";
public static final String DEFAULT_EPISODE_FORMAT = "{n} - {s00e00} - [{absolute}] - [{airdate}] - {t}";
private RSyntaxTextArea editor = createEditor();
private SpinnerNumberModel fromSpinnerModel = new SpinnerNumberModel(1, 0, Integer.MAX_VALUE, 1);
private SpinnerNumberModel toSpinnerModel = new SpinnerNumberModel(20, 0, Integer.MAX_VALUE, 1);
private FileBotList<ListItem> list = new FileBotList<ListItem>();
public ListPanel() {
list.setTitle("Title");
// need a fixed cell size for high performance scrolling
list.getListComponent().setFixedCellHeight(28);
list.getListComponent().getModel().addListDataListener(new PrototypeCellValueUpdater(list.getListComponent(), ""));
list.getRemoveAction().setEnabled(true);
list.setTransferablePolicy(new FileListTransferablePolicy(list::setTitle, this::setFormatTemplate, this::createItemSequence));
FileBotListExportHandler<ListItem> exportHandler = new FileBotListExportHandler<ListItem>(list, (item, out) -> out.println(item.getFormattedValue()));
list.setExportHandler(exportHandler);
list.getTransferHandler().setClipboardHandler(exportHandler);
// context menu
JPopupMenu popup = new JPopupMenu("List");
JMenu menu = new JMenu("Send to");
for (PanelBuilder panel : PanelBuilder.textHandlerSequence()) {
menu.add(newAction(panel.getName(), panel.getIcon(), evt -> {
String text = list.getExportHandler().export();
SwingEventBus.getInstance().post(panel);
invokeLater(200, () -> SwingEventBus.getInstance().post(new StringSelection(text)));
}));
}
popup.add(menu);
popup.addSeparator();
popup.add(newAction("Copy", ResourceManager.getIcon("rename.action.copy"), evt -> {
list.getTransferHandler().getClipboardHandler().exportToClipboard(this, Toolkit.getDefaultToolkit().getSystemClipboard(), TransferHandler.COPY);
}));
popup.add(new SaveAction(list.getExportHandler()));
list.getListComponent().setComponentPopupMenu(popup);
// cell renderer
list.getListComponent().setCellRenderer(new DefaultFancyListCellRenderer() {
@Override
protected void configureListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
super.configureListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
ListItem item = (ListItem) value;
Object object = item.getFormattedValue(); // format just-in-time
if (object instanceof Exception) {
Exception error = (Exception) object;
setText(error.getMessage());
setIcon(ResourceManager.getIcon("status.warning"));
} else {
setText(object.toString());
setIcon(null);
}
}
});
JSpinner fromSpinner = new JSpinner(fromSpinnerModel);
JSpinner toSpinner = new JSpinner(toSpinnerModel);
fromSpinner.setEditor(new NumberEditor(fromSpinner, "#"));
toSpinner.setEditor(new NumberEditor(toSpinner, "#"));
RTextScrollPane editorScrollPane = new RTextScrollPane(editor, false);
editorScrollPane.setLineNumbersEnabled(false);
editorScrollPane.setFoldIndicatorEnabled(false);
editorScrollPane.setIconRowHeaderEnabled(false);
editorScrollPane.setVerticalScrollBarPolicy(RTextScrollPane.VERTICAL_SCROLLBAR_NEVER);
editorScrollPane.setHorizontalScrollBarPolicy(RTextScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
editorScrollPane.setBackground(editor.getBackground());
editorScrollPane.setViewportBorder(createEmptyBorder(2, 2, 2, 2));
editorScrollPane.setOpaque(true);
editorScrollPane.setBorder(new JTextField().getBorder());
setLayout(new MigLayout("nogrid, fill, insets dialog", "align center", "[pref!, center][fill]"));
JLabel patternLabel = new JLabel("Pattern:");
add(patternLabel, "gapbefore indent");
add(editorScrollPane, "gap related, growx, wmin 2cm, h pref!, sizegroupy editor");
add(new JLabel("From:"), "gap 5mm");
add(fromSpinner, "gap related, wmax 15mm, sizegroup spinner, sizegroupy editor");
add(new JLabel("To:"), "gap 5mm");
add(toSpinner, "gap related, wmax 15mm, sizegroup spinner, sizegroupy editor");
add(newButton("Sequence", ResourceManager.getIcon("action.export"), evt -> createItemSequence()), "gap 7mm, gapafter indent, wrap paragraph");
add(list, "grow");
// panel with buttons that will be added inside the list component
JPanel buttonPanel = new JPanel(new MigLayout("insets 1.2mm, nogrid, fill", "align center"));
buttonPanel.add(new JButton(new LoadAction(list::getTransferablePolicy)));
buttonPanel.add(new JButton(new SaveAction(list.getExportHandler())), "gap related");
list.add(buttonPanel, BorderLayout.SOUTH);
// initialize with default values
createItemSequence();
}
private RSyntaxTextArea createEditor() {
RSyntaxTextArea editor = new RSyntaxTextArea(new RSyntaxDocument(SyntaxConstants.SYNTAX_STYLE_GROOVY), "", 1, 80);
editor.setAntiAliasingEnabled(true);
editor.setAnimateBracketMatching(false);
editor.setAutoIndentEnabled(false);
editor.setClearWhitespaceLinesEnabled(false);
editor.setBracketMatchingEnabled(true);
editor.setCloseCurlyBraces(false);
editor.setCodeFoldingEnabled(false);
editor.setHyperlinksEnabled(false);
editor.setUseFocusableTips(false);
editor.setHighlightCurrentLine(false);
editor.setLineWrap(false);
editor.setPaintMarkOccurrencesBorder(false);
editor.setPaintTabLines(false);
editor.setMarkOccurrences(false);
editor.setFont(new Font(MONOSPACED, PLAIN, 14));
Color defaultForeground = editor.getForeground();
// update format on change
editor.getDocument().addDocumentListener(new LazyDocumentListener(20, evt -> {
try {
String expression = editor.getText().trim();
setFormat(expression.isEmpty() ? null : new ExpressionFormat(expression));
editor.setForeground(defaultForeground);
} catch (ScriptException e) {
editor.setForeground(Color.RED);
}
}));
return editor;
}
private ExpressionFormat format;
private String template;
public ListItem createItem(Object object, int i, int from, int to, List<?> context) {
return new ListItem(new IndexedBindingBean(object, i, from, to, context), format);
}
public void setFormat(ExpressionFormat format) {
this.format = format;
// update items
for (ListIterator<ListItem> itr = list.getModel().listIterator(); itr.hasNext();) {
itr.set(new ListItem(itr.next().getBindings(), format));
}
}
public void createItemSequence(List<?> objects) {
List<ListItem> items = IntStream.range(0, objects.size()).mapToObj(i -> createItem(objects.get(i), i + 1, 1, objects.size(), objects)).collect(toList());
list.getListComponent().clearSelection();
list.getModel().clear();
list.getModel().addAll(items);
}
public void createItemSequence() {
int from = fromSpinnerModel.getNumber().intValue();
int to = toSpinnerModel.getNumber().intValue();
List<Integer> context = IntStream.rangeClosed(from, to).boxed().collect(toList());
List<ListItem> items = context.stream().map(it -> createItem(it, it.intValue(), from, to, context)).collect(toList());
setFormatTemplate(DEFAULT_SEQUENCE_FORMAT);
list.setTitle("Sequence");
list.getListComponent().clearSelection();
list.getModel().clear();
list.getModel().addAll(items);
}
public void setFormatTemplate(String format) {
if (template != format) {
template = format;
editor.setText(format);
}
}
@Subscribe
public void handle(Transferable transferable) throws Exception {
TransferablePolicy handler = list.getTransferablePolicy();
if (handler != null && handler.accept(transferable)) {
handler.handleTransferable(transferable, TransferAction.PUT);
}
}
}