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); } } }