/*
* SongNode.java
*
* Copyright (C) 2006-2007 Gabriel Burca (gburca dash virtmus at ebixio dot com)
*
* 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 (at your option) 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package com.ebixio.virtmus;
import com.ebixio.util.WeakPropertyChangeListener;
import com.ebixio.virtmus.actions.SongPdf2JpgAction;
import com.ebixio.virtmus.actions.RenameItemAction;
import com.ebixio.virtmus.actions.SongRemoveAction;
import com.ebixio.virtmus.actions.SongSaveAction;
import com.ebixio.virtmus.actions.SongSaveAsAction;
import java.awt.datatransfer.Transferable;
import java.awt.dnd.DnDConstants;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.List;
import javax.swing.Action;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.openide.ErrorManager;
import org.openide.actions.CopyAction;
import org.openide.actions.CutAction;
import org.openide.actions.MoveDownAction;
import org.openide.actions.MoveUpAction;
import org.openide.actions.NewAction;
import org.openide.actions.PasteAction;
import org.openide.actions.ReorderAction;
import org.openide.nodes.AbstractNode;
import org.openide.nodes.Node;
import org.openide.nodes.NodeTransfer;
import org.openide.nodes.PropertySupport;
import org.openide.nodes.Sheet;
import org.openide.util.NbBundle;
import org.openide.util.WeakListeners;
import org.openide.util.actions.SystemAction;
import org.openide.util.datatransfer.ExTransferable;
import org.openide.util.datatransfer.PasteType;
import org.openide.util.lookup.Lookups;
/**
*
* @author Gabriel Burca <gburca dash virtmus at ebixio dot com>
*/
public class SongNode extends AbstractNode
implements PropertyChangeListener, ChangeListener, Comparable<SongNode> {
private Song song;
/** Creates a new instance of SongNode
* @param playList The playlist the song belongs to
* @param song The song represented by this node
* @param children The music pages belonging to this song
*/
public SongNode(PlayList playList, Song song, MusicPages children) {
super(children, Lookups.fixed(new Object[]{playList, song, children.getIndex()}));
nodeConfig(song);
}
/** Creates a new instance of SongNode. This is used by stand-alone songs
* that are not tied to a specific PlayList (ex: in the Tag TopComponent).
* @param song The song represented by this node
* @param children The music pages belonging to this song
*/
public SongNode(Song song, MusicPages children) {
super(children, Lookups.fixed(new Object[]{song, children.getIndex()}));
nodeConfig(song);
}
/** 2nd stage constructor. */
private void nodeConfig(Song song) {
this.song = song;
displayFormat = new MessageFormat("{0}");
setIconBaseWithExtension("com/ebixio/virtmus/resources/SongNode.png");
song.addPropertyChangeListener(Song.PROP_NAME, new WeakPropertyChangeListener(this, song));
song.addChangeListener(WeakListeners.change(this, song));
}
@Override
public boolean canDestroy() {
PlayList myPlayList = getLookup().lookup(PlayList.class);
return myPlayList != null && myPlayList.type != PlayList.Type.AllSongs;
}
/**
* Called from
* {@link PlayListNode#getDropType(java.awt.datatransfer.Transferable, int, int)}
* when a song is cut (Ctrl-X) from a PlayList.
*
* By the time this function is called, the song has already been added to
* the destination PlayList. We need to find the source PlayList and remove
* the song from it (which should update the nodes).
*
* @see PlayListNode#getDropType(java.awt.datatransfer.Transferable, int, int)
* @throws IOException Just because it can
*/
@Override
public void destroy() throws IOException {
//Song s = getLookup().lookup(Song.class);
PlayList source = getLookup().lookup(PlayList.class);
if (source != null && source.type != PlayList.Type.AllSongs) {
source.removeSong(song);
}
}
@Override
protected Sheet createSheet() {
Sheet sheet = Sheet.createDefault();
Sheet.Set set = Sheet.createPropertiesSet();
Song s = getLookup().lookup(Song.class);
try {
Property nameProp = new PropertySupport.Reflection<String>(s, String.class, "name"); // get/setName
Property fileProp = new PropertySupport.Reflection<File>(s, File.class, "getSourceFile", null); // only getSourceFile
Property tagsProp = new PropertySupport.Reflection<String>(s, String.class, "tags"); // get/setTags
Property notesProp = new PropertySupport.Reflection<String>(s, String.class, "notes"); // get/setNotes
nameProp.setName("Name");
fileProp.setName("Source File");
tagsProp.setName("Tags");
tagsProp.setShortDescription(
NbBundle.getMessage(PlayListTopComponent.class, "CTL_TagsDescription"));
notesProp.setName("Notes");
set.put(nameProp);
set.put(fileProp);
set.put(tagsProp);
set.put(notesProp);
} catch (NoSuchMethodException ex) {
ErrorManager.getDefault().notify(ex);
}
sheet.put(set);
return sheet;
}
@Override
public Action[] getActions(boolean context) {
PlayList pl = getLookup().lookup(PlayList.class);
// Let's not confuse the user by allowing reordering if we won't save
// the new ordering.
if (pl == null || pl.type == PlayList.Type.AllSongs) {
return new Action[] {
SystemAction.get( NewAction.class ),
SystemAction.get( SongSaveAction.class ),
SystemAction.get( SongSaveAsAction.class ),
SystemAction.get( SongRemoveAction.class ),
SystemAction.get( RenameItemAction.class ),
null,
SystemAction.get( CutAction.class ),
SystemAction.get( CopyAction.class ),
SystemAction.get( PasteAction.class ),
null,
SystemAction.get( SongPdf2JpgAction.class ),
};
} else {
return new Action[] {
SystemAction.get( NewAction.class ),
SystemAction.get( SongSaveAction.class ),
SystemAction.get( SongSaveAsAction.class ),
SystemAction.get( SongRemoveAction.class ),
SystemAction.get( RenameItemAction.class ),
null,
SystemAction.get( CutAction.class ),
SystemAction.get( CopyAction.class ),
SystemAction.get( PasteAction.class ),
null,
SystemAction.get( SongPdf2JpgAction.class ),
null,
// We could override these classes and redefine enable().
// See: SongRemoveAction#enable(Node[] nodes)
SystemAction.get( ReorderAction.class ),
SystemAction.get( MoveUpAction.class ),
SystemAction.get( MoveDownAction.class )
};
}
}
public Song getSong() {
return song;
}
// <editor-fold defaultstate="collapsed" desc=" Drag-n-drop ">
/**
* This controls the CutAction context menu availability. If we return false
* the menu option is disabled. If we return true, then it is selectable.
*
* @return true if cutting this node is supported.
* @see #getActions(boolean)
*/
@Override
public boolean canCut() {
return canDestroy();
}
@Override
public boolean canCopy() { return true; }
@Override
protected void createPasteTypes(Transferable t, List<PasteType> s) {
super.createPasteTypes(t, s);
//Log.log("SongNode::createPasteTypes " + t.toString() + " s:" + song.getName());
PasteType paste = getDropType( t, DnDConstants.ACTION_COPY, -1);
if (paste != null) s.add(paste);
}
/* Need to tell NB how to "paste" the node being "dragged" on top of this node
*/
@Override
public PasteType getDropType(Transferable t, final int action, int index) {
if (index != -1) {
//Log.log("SongNode::getDropType " + Integer.toString(index) + " " + Integer.toString(action) + " s:" + song.getName());
}
// dropNode is the node about to be dropped on this SongNode
final Node dropNode = NodeTransfer.node(t, DnDConstants.ACTION_COPY_OR_MOVE + NodeTransfer.CLIPBOARD_CUT);
if (dropNode != null) {
final MusicPage mp = dropNode.getLookup().lookup( MusicPage.class );
// We only accept a MusicPage to be dropped on this SongNode
if (mp != null) {
//Log.log("SongNode::getDropType2 " + Integer.toString(index) + " " + Integer.toString(action) + " s:" + song.getName());
return new PasteType() {
@Override
public Transferable paste() throws IOException {
//song.addPage(new MusicPage(song, mp.getSourceFile()));
song.addPage(mp.clone(song));
if ((action & DnDConstants.ACTION_MOVE) != 0) {
mp.song.removePage(new MusicPage[] {mp});
}
return null;
}
};
}
}
return null;
}
@Override
public Transferable clipboardCut() throws IOException {
Transferable deflt = super.clipboardCut();
ExTransferable added = ExTransferable.create(deflt);
added.put(new ExTransferable.Single(SongFlavor.SONG_FLAVOR) {
@Override
protected Song getData() {
return getLookup().lookup(Song.class);
}
});
return added;
}
@Override
public Transferable clipboardCopy() throws IOException {
Transferable deflt = super.clipboardCopy();
ExTransferable added = ExTransferable.create(deflt);
added.put(new ExTransferable.Single(SongFlavor.SONG_FLAVOR) {
@Override
protected Song getData() {
return getLookup().lookup(Song.class);
}
});
return added;
}
// </editor-fold>
// <editor-fold defaultstate="collapsed" desc=" PropertyChangeListener interface ">
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (Song.PROP_NAME.equals(evt.getPropertyName())) {
String newName = (String)evt.getNewValue();
this.fireDisplayNameChange(null, newName);
}
}
// </editor-fold>
// <editor-fold defaultstate="collapsed" desc=" Node name ">
@Override
public boolean canRename() { return true; }
@Override
public void setName(String nue) {
song.setName(nue);
}
@Override
public String getName() {
return song.getName();
}
@Override
public String getDisplayName() {
return getName();
}
@Override
public String getHtmlDisplayName() {
String name = getDisplayName();
if (song.isDirty()) {
name = "<i>" + name + "</i>";
}
return name;
}
// </editor-fold>
// <editor-fold defaultstate="collapsed" desc=" ChangeListener interface ">
@Override
public void stateChanged(ChangeEvent e) {
fireDisplayNameChange(null, null);
}
// </editor-fold>
@Override
public int compareTo(SongNode o) {
return song.compareTo(o.song);
}
@Override
public String toString() {
return song.getName() + " [" + song.getSourceFile().getAbsolutePath() + "]";
}
}