/*
* Copyright (c) Henrik Niehaus & Lazy Bones development team
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of the project (Lazy Bones) nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
package lazybones.gui.settings.channelpanel;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSpinner;
import javax.swing.JTable;
import javax.swing.SpinnerNumberModel;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.table.DefaultTableModel;
import lazybones.ChannelManager;
import lazybones.LazyBones;
import lazybones.gui.settings.channelpanel.dnd.ListTransferHandler;
import lazybones.gui.settings.channelpanel.dnd.TableTransferHandler;
import lazybones.utils.Utilities;
import org.hampelratte.svdrp.responses.highlevel.Channel;
import org.hampelratte.swing.DecoratableTextField;
import org.hampelratte.swing.TextFieldClearButtonDecorator;
import org.hampelratte.swing.TextFieldHintDecorator;
import util.ui.Localizer;
public class ChannelPanel implements ActionListener {
private DefaultTableModel tableModel;
private JButton up = new JButton();
private JButton down = new JButton();
private JButton assign = new JButton();
private JButton remove = new JButton();
private JButton refresh = new JButton(LazyBones.getTranslation("refresh_channels", "Refresh"));
private JButton autoAssign = new JButton(LazyBones.getTranslation("sort_channels", "Sort"));
private JScrollPane tableScrollpane;
private JScrollPane listScrollpane;
private FilterListModel listModel = new FilterListModel();
private JList<Channel> list = new JList<Channel>(listModel);
private DecoratableTextField channelFilterTextfield = new DecoratableTextField();
private JTable table = new JTable();
private JSpinner minChannelNumber = new JSpinner(new SpinnerNumberModel(0, 0, 10000, 1));
private JSpinner maxChannelNumber = new JSpinner(new SpinnerNumberModel(0, 0, 10000, 1));
public ChannelPanel() {
initComponents();
}
private void initComponents() {
up.setIcon(LazyBones.getInstance().createImageIcon("action", "go-up", 16));
down.setIcon(LazyBones.getInstance().createImageIcon("action", "go-down", 16));
assign.setIcon(LazyBones.getInstance().createImageIcon("action", "go-previous", 16));
remove.setIcon(LazyBones.getInstance().createImageIcon("action", "go-next", 16));
refresh.setIcon(LazyBones.getInstance().createImageIcon("action", "view-refresh", 16));
Object[] headers = { "TV-Browser", "VDR" };
tableModel = new DefaultTableModel(new Object[][] {}, headers) {
@Override
public boolean isCellEditable(int row, int col) {
return false;
}
};
devplugin.Channel[] c = LazyBones.getPluginManager().getSubscribedChannels();
Map<String, Channel> channelMapping = ChannelManager.getChannelMapping();
for (int i = 0; i < c.length; i++) {
Object[] row = { c[i], channelMapping.get(c[i].getId()) };
tableModel.addRow(row);
}
table.setModel(tableModel);
table.setDefaultRenderer(Object.class, new ChannelCellRenderer());
table.getTableHeader().setReorderingAllowed(false);
table.setShowHorizontalLines(false);
table.setRowHeight(23);
table.setDragEnabled(true);
table.setTransferHandler(new TableTransferHandler(list));
list.setCellRenderer(new ChannelListCellrenderer());
list.setDragEnabled(true);
list.setTransferHandler(new ListTransferHandler());
tableScrollpane = new JScrollPane(table);
listScrollpane = new JScrollPane(list);
MinMaxChannelListener minMaxListener = new MinMaxChannelListener();
String minChannel = LazyBones.getProperties().getProperty("minChannelNumber");
minChannelNumber.setValue(new Integer(minChannel));
minChannelNumber.addChangeListener(minMaxListener);
String maxChannel = LazyBones.getProperties().getProperty("maxChannelNumber");
maxChannelNumber.setValue(new Integer(maxChannel));
maxChannelNumber.addChangeListener(minMaxListener);
refresh.addActionListener(this);
autoAssign.addActionListener(this);
up.addActionListener(this);
down.addActionListener(this);
assign.addActionListener(this);
remove.addActionListener(this);
channelFilterTextfield.getDocument().addDocumentListener(new ChannelFilterDocumentListener());
channelFilterTextfield.addDecorator(new TextFieldHintDecorator(LazyBones.getTranslation("filter_channels", "Filter channels")));
channelFilterTextfield.addDecorator(new TextFieldClearButtonDecorator());
}
public JPanel getPanel() {
JPanel panel = new JPanel();
panel.setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.insets = new Insets(5, 5, 5, 5);
// table
gbc.gridx = 0;
gbc.gridy = 0;
gbc.gridwidth = 3;
gbc.gridheight = 3;
gbc.weightx = 1.0;
gbc.weighty = 1.0;
gbc.fill = GridBagConstraints.BOTH;
panel.add(tableScrollpane, gbc);
// channel filter
gbc.gridx = 4;
gbc.gridwidth = 1;
gbc.gridheight = 1;
gbc.weighty = 0;
panel.add(channelFilterTextfield, gbc);
// list
gbc.gridy = 1;
gbc.weighty = 1;
panel.add(listScrollpane, gbc);
// channel interval spinner
JPanel channelInterval = new JPanel();
channelInterval.add(new JLabel(Localizer.getLocalization(Localizer.I18N_CHANNELS)));
channelInterval.add(minChannelNumber);
channelInterval.add(new JLabel("-"));
channelInterval.add(maxChannelNumber);
gbc.gridx = 4;
gbc.gridy = 2;
gbc.weighty = 0.0;
gbc.insets = new Insets(0, 0, 0, 0);
panel.add(channelInterval, gbc);
// assign buttons
JPanel assignButtonPanel = new JPanel(new GridLayout(2, 1, 0, 5));
assignButtonPanel.add(assign);
assignButtonPanel.add(remove);
gbc.gridx = 3;
gbc.gridy = 0;
gbc.gridheight = 2;
gbc.weightx = 0;
gbc.fill = GridBagConstraints.NONE;
gbc.insets = new Insets(5, 5, 5, 5);
panel.add(assignButtonPanel, gbc);
// autoAssign
gbc.gridx = 0;
gbc.gridy = 3;
gbc.weightx = 1.0;
gbc.weighty = 0.0;
gbc.gridheight = 1;
panel.add(autoAssign, gbc);
// up
gbc.gridx = 1;
gbc.gridy = 3;
gbc.weightx = 0.0;
gbc.fill = GridBagConstraints.NONE;
panel.add(up, gbc);
// down
gbc.gridx = 2;
gbc.gridy = 3;
panel.add(down, gbc);
// refresh
gbc.gridx = 4;
gbc.gridy = 3;
gbc.fill = GridBagConstraints.HORIZONTAL;
panel.add(refresh, gbc);
return panel;
}
@Override
public void actionPerformed(ActionEvent e) {
if (e.getSource() == refresh) {
refreshChannelList();
} else if (e.getSource() == autoAssign) {
try {
tryToAssignChannels();
} catch (Exception ex) {
ex.printStackTrace();
}
} else if (e.getSource() == up) {
int[] indices = table.getSelectedRows();
Arrays.sort(indices);
if (indices[0] > 0) {
for (int i = 0; i < indices.length; i++) {
moveUp(indices[i]);
indices[i]--;
}
}
restoreSelection(indices);
if (!Utilities.isCellVisible(table, indices[0], 1)) {
Utilities.scrollToVisible(table, indices[0], 1);
}
} else if (e.getSource() == down) {
int[] indices = table.getSelectedRows();
Arrays.sort(indices);
if (indices[indices.length - 1] < tableModel.getRowCount() - 1) {
for (int i = indices.length - 1; i >= 0; i--) {
moveDown(indices[i]);
indices[i]++;
}
}
restoreSelection(indices);
if (!Utilities.isCellVisible(table, indices[indices.length - 1], 1)) {
Utilities.scrollToVisible(table, indices[indices.length - 1], 1);
}
} else if (e.getSource() == remove) {
int[] rows = table.getSelectedRows();
for (int i = 0; i < rows.length; i++) {
Object channel = table.getValueAt(rows[i], 1);
if (channel != null) {
listModel.addElement((Channel) channel);
table.setValueAt(null, rows[i], 1);
}
}
} else if (e.getSource() == assign) {
int[] rows = list.getSelectedIndices();
int tableRow = table.getSelectedRow();
if (tableRow < 0) {
return;
}
for (int i = rows.length - 1; i >= 0; i--) {
int row = tableRow + i;
if (row > table.getRowCount() - 1) {
continue;
}
Object tableValue = table.getValueAt(row, 1);
if (tableValue != null) {
listModel.addElement((Channel) tableValue);
}
table.setValueAt(listModel.getElementAt(rows[i]), row, 1);
listModel.removeElement(listModel.getElementAt(rows[i]));
}
}
}
private void refreshChannelList() {
// set min and max channel number
LazyBones.getProperties().setProperty("minChannelNumber", minChannelNumber.getValue().toString());
LazyBones.getProperties().setProperty("maxChannelNumber", maxChannelNumber.getValue().toString());
listModel.setMinChannel((int) minChannelNumber.getValue());
listModel.setMaxChannel((int) maxChannelNumber.getValue());
try {
listModel.clear();
ChannelManager.getInstance().update();
List<Channel> vdrchans = ChannelManager.getInstance().getChannels();
if (vdrchans != null) {
// add vdrchannels to channel list
for (Iterator<Channel> iter = vdrchans.iterator(); iter.hasNext();) {
Channel chan = iter.next();
int index = getChannelIndex(chan);
if (index == -1) {
listModel.addElement(chan);
}
}
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
private int getChannelIndex(Channel chan) {
for (int i = 0; i < tableModel.getRowCount(); i++) {
Object o = tableModel.getValueAt(i, 1);
if (o != null && o instanceof Channel) {
Channel c = (Channel) o;
if (c.equals(chan)) {
return i;
}
}
}
return -1;
}
private void restoreSelection(int[] indices) {
table.getSelectionModel().setSelectionInterval(indices[0], indices[0]);
for (int i = 1; i < indices.length; i++) {
table.getSelectionModel().addSelectionInterval(indices[i], indices[i]);
}
}
private void swapCells(int col, int from, int to) {
Object objTo = tableModel.getValueAt(to, col);
Object objFrom = tableModel.getValueAt(from, col);
tableModel.setValueAt(objFrom, to, col);
tableModel.setValueAt(objTo, from, col);
}
private void moveUp(int i) {
swapCells(1, i, i - 1);
}
private void moveDown(int i) {
swapCells(1, i, i + 1);
}
public void saveSettings() {
// set min and max channel number
LazyBones.getProperties().setProperty("minChannelNumber", minChannelNumber.getValue().toString());
LazyBones.getProperties().setProperty("maxChannelNumber", maxChannelNumber.getValue().toString());
Hashtable<String, Channel> channelMapping = new Hashtable<String, Channel>();
for (int i = 0; i < tableModel.getRowCount(); i++) {
devplugin.Channel c = (devplugin.Channel) tableModel.getValueAt(i, 0);
if (c != null) {
String id = c.getId();
Object o = tableModel.getValueAt(i, 1);
if (o != null) {
Channel vdrc = (Channel) o;
channelMapping.put(id, vdrc);
}
}
}
ChannelManager.setChannelMapping(channelMapping);
}
public void tryToAssignChannels() {
for (int i = 0; i < tableModel.getRowCount(); i++) {
ArrayList<Container> list = new ArrayList<Container>();
Object tvbc = tableModel.getValueAt(i, 0);
if (tvbc == null) {
continue;
}
String tvbChan = ((devplugin.Channel) tvbc).getName();
tvbChan = tvbChan.toLowerCase();
for (int j = 0; j < listModel.getSize(); j++) {
Channel chan = listModel.getElementAt(j);
String vdrChan = chan.getName();
vdrChan = vdrChan.toLowerCase();
int percent = Utilities.percentageOfEquality(tvbChan, vdrChan);
list.add(new Container(percent, i, chan));
}
Collections.sort(list);
if (list.size() <= 0) {
return;
}
Container c = list.get(list.size() - 1);
int index = c.getIndex();
Object o = tableModel.getValueAt(index, 1);
if (o == null || "".equals(o.toString())) {
tableModel.setValueAt(c.getChannel(), index, 1);
listModel.removeElement(c.getChannel());
}
}
}
private class ChannelFilterDocumentListener implements DocumentListener {
@Override
public void insertUpdate(DocumentEvent e) {
listModel.setFilter(channelFilterTextfield.getText());
}
@Override
public void removeUpdate(DocumentEvent e) {
listModel.setFilter(channelFilterTextfield.getText());
}
@Override
public void changedUpdate(DocumentEvent e) {
listModel.setFilter(channelFilterTextfield.getText());
}
}
private class MinMaxChannelListener implements ChangeListener {
@Override
public void stateChanged(ChangeEvent e) {
int value = (int) ((JSpinner) e.getSource()).getValue();
if (e.getSource() == minChannelNumber) {
listModel.setMinChannel(value);
} else if (e.getSource() == maxChannelNumber) {
listModel.setMaxChannel(value);
}
}
}
private class Container implements Comparable<Container> {
private int percent;
private int index;
private Channel channel;
Container(int percent, int index, Channel channel) {
this.percent = percent;
this.index = index;
this.channel = channel;
}
@Override
public int compareTo(Container c) {
if (c.getPercent() == percent) {
return 0;
} else if (percent < c.getPercent()) {
return -1;
} else {
return 1;
}
}
public int getPercent() {
return percent;
}
public int getIndex() {
return index;
}
@Override
public String toString() {
return percent + "% " + index + " " + channel.toString();
}
public Channel getChannel() {
return channel;
}
@SuppressWarnings("unused")
public void setChannel(Channel channel) {
this.channel = channel;
}
}
}