/*
* Jajuk
* Copyright (C) The Jajuk Team
* http://jajuk.info
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
*/
package org.jajuk.ui.wizard;
import ext.AutoCompleteDecorator;
import java.awt.Color;
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.StringSelection;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.Vector;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.text.JTextComponent;
import net.miginfocom.swing.MigLayout;
import org.jajuk.base.AlbumArtistManager;
import org.jajuk.base.ArtistManager;
import org.jajuk.base.Device;
import org.jajuk.base.Directory;
import org.jajuk.base.File;
import org.jajuk.base.FileManager;
import org.jajuk.base.GenreManager;
import org.jajuk.base.Item;
import org.jajuk.base.ItemManager;
import org.jajuk.base.Playlist;
import org.jajuk.base.PlaylistManager;
import org.jajuk.base.PropertyMetaInformation;
import org.jajuk.base.Track;
import org.jajuk.base.TrackManager;
import org.jajuk.events.JajukEvent;
import org.jajuk.events.JajukEvents;
import org.jajuk.events.ObservationManager;
import org.jajuk.services.webradio.WebRadio;
import org.jajuk.ui.widgets.CopyableLabel;
import org.jajuk.ui.widgets.InformationJPanel;
import org.jajuk.ui.widgets.JajukJDialog;
import org.jajuk.ui.widgets.OKCancelPanel;
import org.jajuk.ui.windows.JajukMainWindow;
import org.jajuk.util.Const;
import org.jajuk.util.IconLoader;
import org.jajuk.util.JajukIcons;
import org.jajuk.util.Messages;
import org.jajuk.util.UtilGUI;
import org.jajuk.util.UtilString;
import org.jajuk.util.error.CannotRenameException;
import org.jajuk.util.error.JajukException;
import org.jajuk.util.error.NoneAccessibleFileException;
import org.jajuk.util.log.Log;
import org.jdesktop.swingx.JXDatePicker;
import org.jdesktop.swingx.VerticalLayout;
/**
* ItemManager properties dialog for any jajuk item.
*/
public class PropertiesDialog extends JajukJDialog implements ActionListener {
/** The Constant PROPERTIES_WIZARD_6. */
private static final String PROPERTIES_WIZARD_6 = "PropertiesWizard.6";
/** Generated serialVersionUID. */
private static final long serialVersionUID = 1L;
/* Main panel */
private JPanel jpMain;
/** OK/Cancel panel. */
private OKCancelPanel okc;
/** Items. */
private List<Item> alItems;
/** Files filter. */
private Set<File> filter = null;
/** number of editable items (all panels). */
private int iEditable = 0;
/** First property panel. */
private PropertiesPanel panel1;
/** Second property panel. */
private PropertiesPanel panel2;
/** Did user changed something ?. */
private boolean changes = false;
/**
* Constructor for normal wizard with only one wizard panel and n items to
* display.
*
* @param alItems items to display
*/
public PropertiesDialog(List<Item> alItems) {
super(JajukMainWindow.getInstance(), false);
// windows title: name of the element if there is
// only one item, or "selection" word otherwise
if (alItems.size() == 1) {
setTitle(alItems.get(0).getTitle());
} else {
setTitle(Messages.getString(PROPERTIES_WIZARD_6));
}
this.alItems = alItems;
boolean bMerged = false;
if (alItems.size() > 1) {
bMerged = true;
}
panel1 = new PropertiesPanel(alItems, alItems.size() == 1 ? UtilString.getLimitedString(alItems
.get(0).getTitle(), 50) : Messages.getString(PROPERTIES_WIZARD_6) + " [" + alItems.size()
+ "]", bMerged);
// OK/Cancel buttons
okc = new OKCancelPanel(PropertiesDialog.this, Messages.getString("Apply"),
Messages.getString("Close"));
// Add items
jpMain = new JPanel(new MigLayout("insets 5,gapx 5,gapy 5", "[grow]"));
jpMain.add(panel1, "grow,wrap");
jpMain.add(okc, "span,right");
display();
}
/**
* Constructor for file wizard for ie with 2 wizard panels and n items to
* display.
*
* @param alItems1 items to display in the first wizard panel (file for ie)
* @param alItems2 items to display in the second panel (associated track for ie )
*/
public PropertiesDialog(List<Item> alItems1, List<Item> alItems2) {
super();
// windows title: name of the element of only one item, or "selection"
// word otherwise
setTitle(alItems1.size() == 1 ? alItems1.get(0).getTitle() : Messages
.getString(PROPERTIES_WIZARD_6));
this.alItems = alItems1;
if (alItems1.size() > 0) {
// computes filter
refreshFileFilter();
if (alItems1.size() == 1) {
panel1 = new PropertiesPanel(alItems1, UtilString.getLimitedString(alItems1.get(0)
.getTitle(), 50), false);
} else {
panel1 = new PropertiesPanel(alItems1, UtilString.formatPropertyDesc(Messages
.getString(PROPERTIES_WIZARD_6) + " [" + alItems.size() + "]"), true);
}
panel1.setBorder(BorderFactory.createEtchedBorder());
}
if (alItems2.size() > 0) {
if (alItems2.size() == 1) {
panel2 = new PropertiesPanel(alItems2, UtilString.getLimitedString(alItems2.get(0)
.getTitle(), 50), false);
} else {
panel2 = new PropertiesPanel(alItems2, UtilString.formatPropertyDesc(alItems2.size() + " "
+ Messages.getHumanPropertyName(Const.XML_TRACKS)), true);
}
panel2.setBorder(BorderFactory.createEtchedBorder());
}
// OK/Cancel buttons
okc = new OKCancelPanel(this, Messages.getString("Apply"), Messages.getString("Close"));
// Add items
jpMain = new JPanel(new MigLayout("insets 5,gapx 5,gapy 5", "[grow][grow]"));
jpMain.add(panel1, "grow");
// panel2 can be null for a void directory for instance
if (panel2 != null) {
jpMain.add(panel2, "grow,wrap");
}
// Use cell tag because the wrap is not done if panel2 is void
jpMain.add(okc, "cell 0 1 1 1,span,right");
display();
}
/**
* Refresh the file filter used to update only selected files even
* if the associated track is changed and can map several files.
* Note that this method should be called after a file panel save
* because files may have changed then (if user changed the file name).
*/
private void refreshFileFilter() {
// TODO: alItems can be empty here!
if (alItems.get(0) instanceof Directory) {
filter = new HashSet<File>(alItems.size() * 10);
for (Item item : alItems) {
Directory dir = (Directory) item;
filter.addAll(dir.getFilesRecursively());
}
} else if (alItems.get(0) instanceof File) {
filter = new HashSet<File>(alItems.size());
for (Item item : alItems) {
filter.add((File) item);
}
} else {
filter = null;
}
}
/**
* Display.
*/
private void display() {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
UtilGUI.setEscapeKeyboardAction(PropertiesDialog.this, jpMain);
// If none editable item, save button is disabled
if (iEditable == 0) {
okc.getOKButton().setEnabled(false);
}
getRootPane().setDefaultButton(okc.getOKButton());
getContentPane().add(new JScrollPane(jpMain));
pack();
// set default focus to make escape work
okc.getOKButton().requestFocusInWindow();
setLocationRelativeTo(JajukMainWindow.getInstance());
setVisible(true);
}
});
}
/*
* (non-Javadoc)
*
* @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
*/
@Override
public void actionPerformed(ActionEvent e) {
if (e.getSource() == okc.getCancelButton()) {
dispose();
} else if (e.getSource().equals(okc.getOKButton())) {
dispose(); // close window, otherwise you will have some issues if
// fields are not updated with changes
Thread t = new Thread("Properties Wizard Action Thread") {
@Override
public void run() {
try {
panel1.save();
if (panel2 != null) {
// refresh the file filter
refreshFileFilter();
panel2.save();
}
} catch (Exception ex) {
Messages.showErrorMessage(104, ex.getMessage());
Log.error(104, ex.getMessage(), ex);
} finally {
// -UI refresh-
if (changes) {
ObservationManager.notify(new JajukEvent(JajukEvents.DEVICE_REFRESH));
}
}
}
};
// Set min priority to allow EDT to be able to refresh UI between 2 tag
// changes
t.setPriority(Thread.MIN_PRIORITY);
t.start();
}
}
/**
* Tells whether a link button should be shown for a given property.
*
* @param meta
*
* @return true, if checks if is linkable
*/
public boolean isLinkable(PropertyMetaInformation meta) {
// No links for webradios
if (alItems.get(0) instanceof WebRadio) {
return false;
}
String sKey = meta.getName();
return sKey.equals(Const.XML_DEVICE) || sKey.equals(Const.XML_TRACK)
|| sKey.equals(Const.XML_DEVICE) || sKey.equals(Const.XML_TRACK)
|| sKey.equals(Const.XML_ALBUM) || sKey.equals(Const.XML_ARTIST)
|| sKey.equals(Const.XML_YEAR) || sKey.equals(Const.XML_GENRE)
|| sKey.equals(Const.XML_DIRECTORY) || sKey.equals(Const.XML_FILE)
|| sKey.equals(Const.XML_PLAYLIST) || sKey.equals(Const.XML_PLAYLIST_FILE)
|| sKey.equals(Const.XML_FILES) || sKey.equals(Const.XML_PLAYLIST_FILES)
// avoid confusing between music types and device types
|| (sKey.equals(Const.XML_TYPE) && !(alItems.get(0) instanceof Device));
}
/**
* A properties panel.
*/
class PropertiesPanel extends JPanel implements ActionListener {
/** The Constant IDX_NAME. */
private static final int IDX_NAME = 0;
/** The Constant IDX_VALUE. */
private static final int IDX_VALUE = 1;
/** The Constant IDX_COPY. */
private static final int IDX_COPY = 2;
/** The Constant IDX_LINK. */
private static final int IDX_LINK = 3;
/** Generated serialVersionUID. */
private static final long serialVersionUID = 1L;
/** Properties panel. */
JPanel jpProperties;
/** ItemManager description. */
JLabel jlDesc;
/** All dynamic widgets. */
JComponent[][] widgets;
/** Properties to display. */
List<PropertyMetaInformation> alToDisplay;
/** Items. */
List<Item> propItems;
/** Changed properties. */
Map<PropertyMetaInformation, Object> hmPropertyToChange = new HashMap<PropertyMetaInformation, Object>();
/** Merge flag. */
boolean bMerged = false;
/**
* Property panel for single types elements.
*
* @param alItems items to display
* @param sDesc Description (title)
* @param bMerged : whether this panel contains merged values
*/
PropertiesPanel(List<Item> alItems, String sDesc, boolean bMerged) {
super();
this.propItems = alItems;
this.bMerged = bMerged;
Item pa = alItems.get(0);
// first item Process properties to display
alToDisplay = new ArrayList<PropertyMetaInformation>(10);
for (PropertyMetaInformation meta : ItemManager.getItemManager(pa.getClass()).getProperties()) {
// add only editable and non constructor properties
if (meta.isVisible() && (bMerged ? meta.isMergeable() : true)) {
// if more than one item to display, show only mergeable
// properties
alToDisplay.add(meta);
}
}
// contains widgets for properties
// Varname | value | link
widgets = new JComponent[alToDisplay.size()][4];
int index = 0;
for (final PropertyMetaInformation meta : alToDisplay) {
// begin by checking if all items have the same value, otherwise
// we show a void field
boolean bAllEquals = true;
Object oRef = pa.getValue(meta.getName());
for (Item item : alItems) {
if (!item.getValue(meta.getName()).equals(oRef)) {
bAllEquals = false;
break;
}
}
// -Set widgets-
// Property name
String sName = meta.getHumanName();
JLabel jlName = new JLabel(sName + " :");
// Check if property name is translated (for custom
// properties));
if (meta.isCustom()) {
jlName.setForeground(Color.BLUE);
}
// Property value computes editable state
widgets[index][IDX_NAME] = jlName;
// property editable ?
boolean bEditable = meta.isEditable();
// Check meta-data is supported for the file type
if (pa instanceof Track) {
Track track = (Track) pa;
// take any file mapping this track (note all files are of
// the same type)
File file = track.getFiles().get(0);
if (file.getType().getTaggerClass() == null) {
bEditable = false;
}
}
if (!meta.isCustom()) {
// (custom properties are always editable, even for offline
// items)
bEditable = bEditable
&& !(pa instanceof Directory && !((Directory) pa).getDevice().isMounted())
// item is not an unmounted dir
&& !(pa instanceof File
// check item is not an unmounted file
&& !((File) pa).isReady())
// item is not an unmounted playlist
&& !(pa instanceof Playlist && !((Playlist) pa).isReady());
}
if (bEditable) {
iEditable++;
if (meta.getType().equals(Date.class)) {
final JXDatePicker jdp = new JXDatePicker();
jdp.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
Object oValue = jdp.getDate();
hmPropertyToChange.put(meta, oValue);
}
});
if (bAllEquals) {
// If several items, take first value found
jdp.setDate(new Date(pa.getDateValue(meta.getName()).getTime()));
} else {
// Make sure to set default date to 1970, not today to allow user
// to set date to today for multiple selection and to allow jajuk
// to detect a change
jdp.setDate(new Date(0));
}
widgets[index][IDX_VALUE] = jdp;
} else if (meta.getType().equals(Boolean.class)) {
// for a boolean, value is a checkbox
final JCheckBox jcb = new JCheckBox();
jcb.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
Object oValue = jcb.isSelected();
hmPropertyToChange.put(meta, oValue);
}
});
if (bAllEquals) {
jcb.setSelected(pa.getBooleanValue(meta.getName()));
}
// if some elements are different, set opposite value of
// first item to allow change
else {
jcb.setSelected(!pa.getBooleanValue(meta.getName()));
}
widgets[index][IDX_VALUE] = jcb;
} else if (meta.getType().equals(Double.class) || meta.getType().equals(Integer.class)
|| meta.getType().equals(Long.class)) {
// Note : we manage field validation by ourself, and we
// don't use formatted textfields because they display
// numbers with comas (this is wrong to display
// years for instance)
final JTextField jtfValue;
jtfValue = new JTextField();
jtfValue.addKeyListener(new KeyAdapter() {
@Override
public void keyReleased(KeyEvent arg0) {
if (jtfValue.getText().length() == 0) {
hmPropertyToChange.remove(meta);
return;
}
Object oValue = null;
try {
if (meta.getType().equals(Long.class)) {
oValue = Long.parseLong(jtfValue.getText());
} else if (meta.getType().equals(Double.class)) {
oValue = Double.parseDouble(jtfValue.getText());
} else if (meta.getType().equals(Integer.class)) {
oValue = Integer.parseInt(jtfValue.getText());
}
} catch (Exception e) {
Log.error(137, meta.getName(), null);
jtfValue.setText("");
Messages.showErrorMessage(137, meta.getName());
hmPropertyToChange.remove(meta);
return;
}
hmPropertyToChange.put(meta, oValue);
}
});
if (bAllEquals) {
jtfValue.setText(pa.getHumanValue(meta.getName()));
// If several items, take first value found
}
widgets[index][IDX_VALUE] = jtfValue;
} else if (meta.getType().equals(String.class)
// for genres
&& meta.getName().equals(Const.XML_GENRE)) {
Vector<String> genres = GenreManager.getInstance().getGenresList();
final JComboBox jcb = new JComboBox(genres);
jcb.setEditable(true);
AutoCompleteDecorator.decorate(jcb);
// set current genre to combo
int i = -1;
int comp = 0;
String sCurrentGenre = pa.getHumanValue(Const.XML_GENRE);
for (String s : genres) {
if (s.equals(sCurrentGenre)) {
i = comp;
break;
}
comp++;
}
jcb.setSelectedIndex(i);
// if different genre, don't show anything
if (!bAllEquals) {
jcb.setSelectedIndex(-1);
}
jcb.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
Object oValue = jcb.getSelectedItem();
if (oValue == null || ((String) oValue).trim().length() == 0) {
// can occur during ui interaction
return;
}
// check that string length > 0
if (((String) oValue).length() < 1) {
jcb.setSelectedIndex(-1);
Log.error(137, meta.getName(), null);
Messages.showErrorMessage(137, meta.getName());
return;
}
hmPropertyToChange.put(meta, oValue);
}
});
widgets[index][IDX_VALUE] = jcb;
} else if (meta.getType().equals(String.class)
&& (Const.XML_ARTIST.equals(meta.getName()) || Const.XML_ALBUM_ARTIST.equals(meta
.getName()))) {
// for artists or album-artists
Vector<String> artists = null;
// This string is the artist or the album artist value, used to find combo box index to set
String valueToCheck = null;
if (Const.XML_ARTIST.equals(meta.getName())) {
artists = ArtistManager.getArtistsList();
valueToCheck = pa.getHumanValue(Const.XML_ARTIST);
} else if (Const.XML_ALBUM_ARTIST.equals(meta.getName())) {
artists = AlbumArtistManager.getAlbumArtistsList();
valueToCheck = pa.getHumanValue(Const.XML_ALBUM_ARTIST);
}
if (artists == null) {
throw new IllegalStateException("Could not get a list of Artists!");
}
final JComboBox jcb = new JComboBox(artists);
jcb.setEditable(true);
AutoCompleteDecorator.decorate(jcb);
// set current genre to combo
int i = -1;
int comp = 0;
for (String s : artists) {
if (s.equals(valueToCheck)) {
i = comp;
break;
}
comp++;
}
jcb.setSelectedIndex(i);
// if different artist, don't show anything
if (!bAllEquals) {
jcb.setSelectedIndex(-1);
}
jcb.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
Object oValue = jcb.getSelectedItem();
if (oValue == null || ((String) oValue).trim().length() == 0) {
// can occur during ui interaction
return;
}
// check that string length > 0
if (((String) oValue).length() < 1) {
jcb.setSelectedIndex(-1);
Log.error(137, meta.getName(), null);
Messages.showErrorMessage(137, meta.getName());
return;
}
hmPropertyToChange.put(meta, oValue);
}
});
widgets[index][IDX_VALUE] = jcb;
} else { // for all others formats (string, class)
final JTextField jtfValue = new JTextField();
jtfValue.addKeyListener(new KeyAdapter() {
@Override
public void keyReleased(KeyEvent arg0) {
if (jtfValue.getText().length() == 0) {
hmPropertyToChange.remove(meta);
return;
}
String value = jtfValue.getText();
hmPropertyToChange.put(meta, value);
}
});
if (bAllEquals) {
// If several items, take first value found
jtfValue.setText(pa.getHumanValue(meta.getName()));
}
widgets[index][IDX_VALUE] = jtfValue;
}
} else {
CopyableLabel jl = null;
if (meta.getName().equals(Const.XML_ALBUM_DISC_ID)) {
// Specific rendering : the album disc id should be translated from decimal to hex
jl = new CopyableLabel((Long.toString(((Long) pa.getValue(meta.getName())), 16)));
} else {
// Regular un-editable item rendering
jl = new CopyableLabel(pa.getHumanValue(meta.getName()));
}
// If several items, take first value found
if (bAllEquals) {
String s = pa.getHumanValue(meta.getName());
if (s.indexOf(',') != -1) {
String[] sTab = s.split(",");
StringBuilder sb = new StringBuilder();
sb.append("<html>");
for (String element : sTab) {
sb.append("<p>").append(element).append("</p>");
}
sb.append("</html>");
jl.setToolTipText(sb.toString());
} else {
jl.setToolTipText(s);
}
}
widgets[index][IDX_VALUE] = jl;
}
// Link
if (isLinkable(meta)) {
JButton jbLink = new JButton(IconLoader.getIcon(JajukIcons.PROPERTIES));
jbLink.addActionListener(this);
jbLink.setActionCommand("link");
// Not focusable to avoid tabbing between field focus this button instead next field
jbLink.setFocusable(false);
jbLink.setToolTipText(Messages.getString("PropertiesWizard.12"));
widgets[index][IDX_LINK] = jbLink;
}
// Copy
if (isCopyable(widgets[index][IDX_VALUE])) {
JButton jbCopy = new JButton(IconLoader.getIcon(JajukIcons.COPY_TO_CLIPBOARD));
jbCopy.addActionListener(this);
jbCopy.setActionCommand("copy");
// Not focusable to avoid tabbing between field focus this button instead next field
jbCopy.setFocusable(false);
jbCopy.setToolTipText(Messages.getString("PropertiesWizard.14"));
widgets[index][IDX_COPY] = jbCopy;
}
index++;
}
// Add title
JLabel jlName = new JLabel("<html><b>" + Messages.getString("PropertiesWizard.1")
+ "</b></html>");
JLabel jlValue = new JLabel("<html><b>" + Messages.getString("PropertiesWizard.2")
+ "</b></html>");
JLabel jlLink = new JLabel("<html><b>" + Messages.getString("PropertiesWizard.4")
+ "</b></html>");
JLabel jlCopy = new JLabel("<html><b>" + Messages.getString("PropertiesWizard.13")
+ "</b></html>");
jpProperties = new JPanel(new MigLayout("insets 10,gapx 5,gapy 10", "[][grow][]"));
jpProperties.add(jlName);
jpProperties.add(jlValue, "grow");
jpProperties.add(jlCopy, "");
jpProperties.add(jlLink, "wrap");
// Add widgets
int i = 0;
int j = 4;
// for (PropertyMetaInformation meta : alToDisplay) {
for (int k = 0; k < alToDisplay.size(); k++) {
jpProperties.add(widgets[i][IDX_NAME]);
if (widgets[i][IDX_LINK] == null) { // link widget can be null
if (widgets[i][IDX_COPY] == null) {
jpProperties.add(widgets[i][IDX_VALUE], "grow,width 200:200, wrap");
} else {
jpProperties.add(widgets[i][IDX_VALUE], "grow,width 200:200");
jpProperties.add(widgets[i][IDX_COPY], "wrap");
}
} else {
if (widgets[i][IDX_COPY] == null) {
jpProperties.add(widgets[i][IDX_VALUE], "grow,width 200:200");
jpProperties.add(widgets[i][IDX_LINK], "wrap");
} else {
jpProperties.add(widgets[i][IDX_VALUE], "grow,width 200:200");
jpProperties.add(widgets[i][IDX_COPY], "");
jpProperties.add(widgets[i][IDX_LINK], "wrap");
}
}
i++;
j += 2;
}
setLayout(new VerticalLayout(10));
// desc
jlDesc = new JLabel(UtilString.formatPropertyDesc(sDesc));
add(jlDesc);
add(jpProperties);
}
/**
* Checks if is copyable.
*
* @param jComponent
* @return true, if is copyable
*/
private boolean isCopyable(JComponent jComponent) {
if (jComponent instanceof JXDatePicker || jComponent instanceof JTextComponent
|| jComponent instanceof JComboBox || jComponent instanceof JLabel) {
// ignore some useless values...
if ("0".equals(extractValue(jComponent))) {
return false;
}
return true;
}
return false;
}
/*
* (non-Javadoc)
*
* @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
*/
@Override
public void actionPerformed(ActionEvent ae) {
// Link
if (ae.getActionCommand().equals("link")) {
PropertyMetaInformation meta = alToDisplay.get(getWidgetIndex((JComponent) ae.getSource()));
String sProperty = meta.getName();
if (Const.XML_FILES.equals(sProperty)) {
Track track = (Track) propItems.get(0);
List<Item> alFiles = new ArrayList<Item>(1);
alFiles.addAll(track.getFiles());
// show properties window for this item
new PropertiesDialog(alFiles);
} else if (Const.XML_PLAYLIST_FILES.equals(sProperty)) {
// can only be a set a files
String sValue = propItems.get(0).getStringValue(sProperty);
StringTokenizer st = new StringTokenizer(sValue, ",");
List<Item> items = new ArrayList<Item>(3);
while (st.hasMoreTokens()) {
String sPlf = st.nextToken();
Item pa = PlaylistManager.getInstance().getPlaylistByID(sPlf);
if (pa != null) {
items.add(pa);
}
}
new PropertiesDialog(items);
// show properties window for this item
} else {
String sValue = propItems.get(0).getStringValue(sProperty);
// can be only an ID
Item pa = ItemManager.getItemManager(sProperty).getItemByID(sValue);
if (pa != null) {
List<Item> items = new ArrayList<Item>(1);
items.add(pa);
// show properties window for this item
new PropertiesDialog(items);
}
}
} else if (ae.getActionCommand().equals("copy")) {
int i = getWidgetIndex((JComponent) ae.getSource());
JComponent jComponent = widgets[i][IDX_VALUE];
StringSelection data = new StringSelection(extractValue(jComponent));
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
clipboard.setContents(data, data);
}
}
/**
* Extract value.
*
*
* @param jComponent
* @return the string
*/
private String extractValue(JComponent jComponent) {
String sValue = null;
if (jComponent instanceof JXDatePicker) {
sValue = ((JXDatePicker) jComponent).getDate().toString();
} else if (jComponent instanceof JTextComponent) {
sValue = ((JTextComponent) jComponent).getText();
} else if (jComponent instanceof JComboBox) {
sValue = ((JComboBox) jComponent).getSelectedItem().toString();
} else if (jComponent instanceof JLabel) {
sValue = ((JLabel) jComponent).getText();
} else {
throw new IllegalArgumentException("Unexpected type of component: "
+ jComponent.getClass().getName());
}
return sValue;
}
/**
* Save changes in tags.
*/
protected void save() {
try {
UtilGUI.waiting();
// We remove autocommit state to group commit to tags for several tags
// of a single file
TrackManager.getInstance().setAutocommit(false);
Object oValue = null;
Item newItem = null;
// list of actually changed tracks (used by out message)
List<PropertyMetaInformation> alChanged = new ArrayList<PropertyMetaInformation>(2);
// none change, leave
if (hmPropertyToChange.size() == 0) {
return;
}
try {
// Computes all items to change
// contains items to be changed
List<Item> itemsToChange = new ArrayList<Item>(propItems);
// Items in error
List<Item> alInError = new ArrayList<Item>(itemsToChange.size());
// details for errors
String sDetails = "";
// Check typed value format, display error message only once per
// property
for (PropertyMetaInformation meta : hmPropertyToChange.keySet()) {
// New value
oValue = hmPropertyToChange.get(meta);
// Check it is not null for non custom properties. Note that
// we also allow void values for comments
if (oValue == null || (oValue.toString().trim().length() == 0)
&& !(meta.isCustom() || meta.getName().equals(Const.XML_TRACK_COMMENT))) {
Log.error(137, '{' + meta.getName() + '}', null);
Messages.showErrorMessage(137, '{' + meta.getName() + '}');
return;
}
}
// Now we have all items to consider, write tags for each
// property to change
for (int i = 0; i < itemsToChange.size(); i++) {
// Note that item object can be changed during the next for loop, so
// do not declare it there
Item item = null;
for (PropertyMetaInformation meta : hmPropertyToChange.keySet()) {
item = itemsToChange.get(i);
// New value
oValue = hmPropertyToChange.get(meta);
// Old value
String sOldValue = item.getHumanValue(meta.getName());
if (!UtilString.format(oValue, meta, true).equals(sOldValue)) {
try {
newItem = ItemManager.changeItem(item, meta.getName(), oValue, filter);
changes = true;
}
// none accessible file for this track, for this error,
// we display an error and leave completely
catch (NoneAccessibleFileException none) {
Log.error(none);
Messages.showErrorMessage(none.getCode(), item.getHumanValue(Const.XML_NAME));
// close window to avoid reseting all properties to
// old values
dispose();
return;
}
// cannot rename file, for this error, we display an
// error and leave completely
catch (CannotRenameException cre) {
Log.error(cre);
Messages.showErrorMessage(cre.getCode());
dispose();
return;
}
// probably error writing a tag, store track reference
// and continue
catch (JajukException je) {
Log.error(je);
if (!alInError.contains(item)) {
alInError.add(item);
// create details label with 3 levels deep
sDetails += buidlDetailsString(je);
}
continue;
}
// if this item was element of property panel elements,
// update it
if (propItems.contains(item)) {
propItems.remove(item);
propItems.add(newItem);
}
// Update itemsToChange to replace the item. Indeed, if we change
// several properties to the same item, the item itself must
// change
itemsToChange.set(i, newItem);
// if individual item, change title in case of
// constructor element change
if (!bMerged) {
jlDesc.setText(UtilString.formatPropertyDesc(newItem.getTitle()));
}
// note this property have been changed
if (!alChanged.contains(meta)) {
alChanged.add(meta);
}
}
}
// Require full commit for all changed tags on the current file
try {
TrackManager.getInstance().commit();
} catch (Exception e) {
Log.error(e);
if (!alInError.contains(item)) {
alInError.add(item);
// create details label with 3 levels deep
sDetails += buidlDetailsString(e);
}
}
}
// display a message for file write issues
if (alInError.size() > 0) {
String sInfo = "";
int index = 0;
for (Item item : alInError) {
// limit number of errors
if (index == 15) {
sInfo += "\n...";
break;
}
sInfo += "\n" + item.getHumanValue(Const.XML_NAME);
index++;
}
Messages.showDetailedErrorMessage(104, sInfo, sDetails);
}
// display a message if user changed at least one property
if (alChanged.size() > 0) {
StringBuilder sbChanged = new StringBuilder();
sbChanged.append("{ ");
for (PropertyMetaInformation meta : alChanged) {
sbChanged.append(meta.getHumanName()).append(' ');
}
sbChanged.append('}');
InformationJPanel.getInstance().setMessage(
alChanged.size() + " " + Messages.getString("PropertiesWizard.10") + ": "
+ sbChanged.toString(), InformationJPanel.MessageType.INFORMATIVE);
}
} finally {
// Force files resorting to ensure the sorting consistency, indeed,
// files are sorted by name *and* track order and we need to force a
// files resort after an order change (this is already done in case of
// file name change)
FileManager.getInstance().forceSorting();
}
} finally {
UtilGUI.stopWaiting();
// Reset auto-commit state
TrackManager.getInstance().setAutocommit(true);
}
}
/**
* Build the errors details message.
*
* @param e the exception
*
* @return the errors details message
*/
private String buidlDetailsString(Exception e) {
String sDetails = e.getMessage();
if (e.getCause() != null) {
sDetails += "\nCaused by:" + e.getCause();
if (e.getCause().getCause() != null) {
sDetails += "\nCaused by:" + e.getCause().getCause();
if (e.getCause().getCause().getCause() != null) {
sDetails += "\nCaused by:" + e.getCause().getCause().getCause();
}
}
}
sDetails += "\n\n";
return sDetails;
}
/**
* Gets the widget index.
*
* @param widget
*
* @return index of a given widget in the widget table
*/
private int getWidgetIndex(JComponent widget) {
int resu = -1;
for (int row = 0; row < widgets.length; row++) {
for (int col = 0; col < widgets[0].length; col++) {
if (widget.equals(widgets[row][col])) {
resu = row;
break;
}
}
}
return resu;
}
}
}