package net.filebot.ui.episodelist;
import static net.filebot.ui.episodelist.SeasonSpinnerModel.*;
import static net.filebot.util.ui.SwingUI.*;
import static net.filebot.web.EpisodeUtilities.*;
import java.awt.Dimension;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import javax.swing.AbstractAction;
import javax.swing.Icon;
import javax.swing.JComboBox;
import javax.swing.JMenu;
import javax.swing.JPopupMenu;
import javax.swing.JSpinner;
import javax.swing.KeyStroke;
import javax.swing.TransferHandler;
import net.filebot.Language;
import net.filebot.ResourceManager;
import net.filebot.Settings;
import net.filebot.WebServices;
import net.filebot.media.MediaDetection;
import net.filebot.similarity.Normalization;
import net.filebot.ui.AbstractSearchPanel;
import net.filebot.ui.FileBotList;
import net.filebot.ui.LanguageComboBox;
import net.filebot.ui.PanelBuilder;
import net.filebot.ui.SelectDialog;
import net.filebot.ui.transfer.SaveAction;
import net.filebot.util.ui.LabelProvider;
import net.filebot.util.ui.SelectButton;
import net.filebot.util.ui.SimpleLabelProvider;
import net.filebot.util.ui.SwingEventBus;
import net.filebot.web.Episode;
import net.filebot.web.EpisodeListProvider;
import net.filebot.web.SearchResult;
import net.filebot.web.SeasonOutOfBoundsException;
import net.filebot.web.SortOrder;
public class EpisodeListPanel extends AbstractSearchPanel<EpisodeListProvider, Episode> {
private SeasonSpinnerModel seasonSpinnerModel = new SeasonSpinnerModel();
private LanguageComboBox languageComboBox = new LanguageComboBox(Language.getLanguage("en"), getSettings());
private JComboBox sortOrderComboBox = new JComboBox(SortOrder.values());
public EpisodeListPanel() {
historyPanel.setColumnHeader(0, "TV Series");
historyPanel.setColumnHeader(1, "Number of Episodes");
JSpinner seasonSpinner = new JSpinner(seasonSpinnerModel);
seasonSpinner.setEditor(new SeasonSpinnerEditor(seasonSpinner));
// set minimum size to "All Seasons" preferred size
Dimension d = seasonSpinner.getPreferredSize();
d.width += 12;
seasonSpinner.setMinimumSize(d);
// add after text field
add(seasonSpinner, "sgy button, gap indent", 1);
add(sortOrderComboBox, "sgy button, gap rel", 2);
add(languageComboBox, "sgy button, gap indent+5", 3);
searchTextField.getSelectButton().addPropertyChangeListener(SelectButton.SELECTED_VALUE, selectButtonListener);
installAction(this, KeyStroke.getKeyStroke(KeyEvent.VK_UP, KeyEvent.SHIFT_MASK), new SpinSeasonAction(1));
installAction(this, KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, KeyEvent.SHIFT_MASK), new SpinSeasonAction(-1));
}
@Override
protected Collection<String> getHistory(EpisodeListProvider engine) throws Exception {
final List<String> names = new ArrayList<String>(100000);
final SearchResult[] index = (engine == WebServices.AniDB) ? MediaDetection.releaseInfo.getAnidbIndex() : MediaDetection.releaseInfo.getTheTVDBIndex();
for (SearchResult it : index) {
for (String n : it.getEffectiveNames()) {
names.add(Normalization.removeTrailingBrackets(n));
}
}
return names;
}
@Override
protected EpisodeListProvider[] getSearchEngines() {
return WebServices.getEpisodeListProviders();
}
@Override
protected LabelProvider<EpisodeListProvider> getSearchEngineLabelProvider() {
return SimpleLabelProvider.forClass(EpisodeListProvider.class);
}
@Override
protected Settings getSettings() {
return Settings.forPackage(EpisodeListPanel.class);
}
@Override
protected EpisodeListRequestProcessor createRequestProcessor() {
EpisodeListProvider provider = searchTextField.getSelectButton().getSelectedValue();
String text = searchTextField.getText().trim();
int season = seasonSpinnerModel.getSeason();
SortOrder order = (SortOrder) sortOrderComboBox.getSelectedItem();
Locale language = languageComboBox.getModel().getSelectedItem().getLocale();
return new EpisodeListRequestProcessor(new EpisodeListRequest(provider, text, season, order, language));
};
private final PropertyChangeListener selectButtonListener = new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
EpisodeListProvider provider = searchTextField.getSelectButton().getSelectedValue();
// lock season spinner on "All Seasons" if provider doesn't support fetching of single seasons
if (!provider.hasSeasonSupport()) {
seasonSpinnerModel.lock(ALL_SEASONS);
} else {
seasonSpinnerModel.unlock();
}
}
};
private class SpinSeasonAction extends AbstractAction {
public SpinSeasonAction(int spin) {
super(String.format("Spin%+d", spin));
putValue("spin", spin);
}
@Override
public void actionPerformed(ActionEvent e) {
seasonSpinnerModel.spin((Integer) getValue("spin"));
}
}
protected static class EpisodeListRequest extends Request {
public final EpisodeListProvider provider;
public final int season;
public final SortOrder order;
public final Locale language;
public EpisodeListRequest(EpisodeListProvider provider, String searchText, int season, SortOrder order, Locale language) {
super(searchText);
this.provider = provider;
this.season = season;
this.order = order;
this.language = language;
}
}
protected static class EpisodeListRequestProcessor extends RequestProcessor<EpisodeListRequest, Episode> {
public EpisodeListRequestProcessor(EpisodeListRequest request) {
super(request, new EpisodeListTab());
}
@Override
public Collection<SearchResult> search() throws Exception {
return request.provider.search(request.getSearchText(), request.language);
}
@Override
public Collection<Episode> fetch() throws Exception {
List<Episode> episodes = request.provider.getEpisodeList(getSearchResult(), request.order, request.language);
if (request.season != ALL_SEASONS) {
List<Episode> episodeForSeason = filterBySeason(episodes, request.season);
if (episodeForSeason.isEmpty()) {
throw new SeasonOutOfBoundsException(getSearchResult().getName(), request.season, getLastSeason(episodes));
}
episodes = episodeForSeason;
}
return episodes;
}
@Override
public URI getLink() {
return request.provider.getEpisodeListLink(getSearchResult());
}
@Override
public void process(Collection<Episode> episodes) {
// set a proper title for the export handler before adding episodes
getComponent().setTitle(getTitle());
getComponent().getModel().addAll(episodes);
}
@Override
public String getStatusMessage(Collection<Episode> result) {
return (result.isEmpty()) ? "No episodes found" : String.format("%d episodes", result.size());
}
@Override
public EpisodeListTab getComponent() {
return (EpisodeListTab) super.getComponent();
}
@Override
public String getTitle() {
if (request.season == ALL_SEASONS)
return super.getTitle();
// add additional information to default title
return String.format("%s - Season %d", super.getTitle(), request.season);
}
@Override
public Icon getIcon() {
return request.provider.getIcon();
}
@Override
protected void configureSelectDialog(SelectDialog<SearchResult> selectDialog) {
super.configureSelectDialog(selectDialog);
selectDialog.getMessageLabel().setText("Select a Show:");
}
}
protected static class EpisodeListTab extends FileBotList<Episode> {
public EpisodeListTab() {
// initialize dnd and clipboard export handler for episode list
EpisodeListExportHandler exportHandler = new EpisodeListExportHandler(this);
setExportHandler(exportHandler);
getTransferHandler().setClipboardHandler(exportHandler);
// allow removal of episode list entries
getRemoveAction().setEnabled(true);
// remove borders
listScrollPane.setBorder(null);
setBorder(null);
// popup menu
JPopupMenu popup = new JPopupMenu("Episodes");
JMenu menu = new JMenu("Send to");
for (PanelBuilder panel : PanelBuilder.episodeHandlerSequence()) {
menu.add(newAction(panel.getName(), panel.getIcon(), evt -> {
// switch to Rename panel
SwingEventBus.getInstance().post(panel);
// load episode data
invokeLater(200, () -> SwingEventBus.getInstance().post(exportHandler.export(this, false)));
}));
}
popup.add(menu);
popup.addSeparator();
popup.add(newAction("Copy", ResourceManager.getIcon("rename.action.copy"), evt -> {
getTransferHandler().getClipboardHandler().exportToClipboard(this, Toolkit.getDefaultToolkit().getSystemClipboard(), TransferHandler.COPY);
}));
popup.add(new SaveAction(getExportHandler()));
getListComponent().setComponentPopupMenu(popup);
}
}
}