/*
* 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.views;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.MouseEvent;
import java.util.HashSet;
import java.util.Set;
import javax.swing.BoxLayout;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JToolBar;
import javax.swing.SwingUtilities;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import net.miginfocom.swing.MigLayout;
import org.jajuk.base.File;
import org.jajuk.events.JajukEvent;
import org.jajuk.events.JajukEvents;
import org.jajuk.events.ObservationManager;
import org.jajuk.services.lyrics.LyricsService;
import org.jajuk.services.lyrics.providers.GenericWebLyricsProvider;
import org.jajuk.services.lyrics.providers.ILyricsProvider;
import org.jajuk.services.lyrics.providers.JajukLyricsProvider;
import org.jajuk.services.lyrics.providers.TxtLyricsProvider;
import org.jajuk.services.players.QueueModel;
import org.jajuk.services.webradio.WebRadio;
import org.jajuk.ui.actions.ActionManager;
import org.jajuk.ui.actions.JajukActions;
import org.jajuk.ui.helpers.FontManager;
import org.jajuk.ui.helpers.FontManager.JajukFont;
import org.jajuk.ui.helpers.JajukMouseAdapter;
import org.jajuk.ui.widgets.JajukButton;
import org.jajuk.ui.widgets.JajukJToolbar;
import org.jajuk.ui.widgets.JajukToggleButton;
import org.jajuk.util.Conf;
import org.jajuk.util.Const;
import org.jajuk.util.IconLoader;
import org.jajuk.util.JajukIcons;
import org.jajuk.util.Messages;
import org.jajuk.util.UtilFeatures;
import org.jajuk.util.UtilGUI;
import org.jajuk.util.UtilSystem;
import org.jajuk.util.error.LyricsPersistenceException;
import org.jajuk.util.log.Log;
/**
* Lyrics view
* <p>
* Data comes from the Tag of the file or a txt file if present; otherwise from
* www.lyrc.com.ar, lyrics.wikia.com or lyricsfly.com
* </p>
*/
public class LyricsView extends ViewAdapter implements DocumentListener {
/** Generated serialVersionUID. */
private static final long serialVersionUID = 2229941034734574056L;
private JTextArea jtaLyrics;
private JScrollPane jspLyrics;
private JLabel jlTitle;
private String sURL;
/** Currently analyzed file. */
private volatile File file;
private String lyrics;
private JMenuItem jmiCopyToClipboard;
private JMenuItem jmiLaunchInBrowser;
private JPanel jpMain;
private JajukButton jbSave;
private JajukButton jbDelete;
private JajukToggleButton jtbEdit;
/** Edition toolbar. */
private JToolBar toolbarEdit;
private boolean changeDetected = false;
/**
* .
*/
class LyricsUpdateThread extends Thread {
/**
* Instantiates a new lyrics update thread.
*/
LyricsUpdateThread() {
super("Lyrics Update Thread-" + file.getTrack().getArtist().getName2() + "-"
+ file.getTrack().getName());
}
/* (non-Javadoc)
* @see java.lang.Thread#run()
*/
@Override
public void run() {
// Launch lyrics service asynchronously and out of the
// AWT dispatcher thread
lyrics = LyricsService.getLyrics(file);
if (lyrics != null) {
ILyricsProvider provider = LyricsService.getCurrentProvider();
sURL = provider.getSourceAddress();
} else {
sURL = "<none>";
}
// Notify to make UI changes
ObservationManager.notify(new JajukEvent(JajukEvents.LYRICS_DOWNLOADED));
}
}
/**
* Instantiates a new lyrics view.
*/
public LyricsView() {
super();
}
/*
* (non-Javadoc)
*
* @see org.jajuk.ui.IView#initUI()
*/
@Override
public void initUI() {
final FontManager fmgr = FontManager.getInstance();
jtaLyrics = new JTextArea();
jtaLyrics.setFont(fmgr.getFont(JajukFont.PLAIN));
jlTitle = new JLabel();
jlTitle.setFont(fmgr.getFont(JajukFont.PLAIN_L));
jspLyrics = new JScrollPane(jtaLyrics);
jtaLyrics.setLineWrap(true);
jtaLyrics.setWrapStyleWord(true);
jtaLyrics.setEditable(false);
jtaLyrics.setMargin(new Insets(10, 10, 10, 10));
jtaLyrics.setFont(fmgr.getFont(JajukFont.BOLD));
jtaLyrics.addMouseListener(new JajukMouseAdapter() {
@Override
public void handlePopup(final MouseEvent e) {
final JPopupMenu menu = new JPopupMenu();
menu.add(jmiCopyToClipboard);
if (UtilSystem.isBrowserSupported()) {
jmiLaunchInBrowser.putClientProperty(Const.DETAIL_CONTENT, sURL);
jmiCopyToClipboard.putClientProperty(Const.DETAIL_CONTENT, sURL);
menu.add(jmiLaunchInBrowser);
}
menu.show(jtaLyrics, e.getX(), e.getY());
}
});
// Detect text area content change to enable save button on changes
jtaLyrics.getDocument().addDocumentListener(this);
initEditUI();
//Create a toolbar to group edition commands
toolbarEdit = new JajukJToolbar();
toolbarEdit.add(jtbEdit);
toolbarEdit.add(jbSave);
toolbarEdit.add(jbDelete);
// Menu items
jmiCopyToClipboard = new JMenuItem(ActionManager.getAction(JajukActions.COPY_TO_CLIPBOARD));
if (UtilSystem.isBrowserSupported()) {
jmiLaunchInBrowser = new JMenuItem(ActionManager.getAction(JajukActions.LAUNCH_IN_BROWSER));
}
// Add items
jpMain = new JPanel(new MigLayout("insets 5,gapx 3, gapy 5,filly", "[95][grow]", "[][grow]"));
jpMain.add(jtbEdit, "left,split 3");
jpMain.add(jbSave, "left");
jpMain.add(jbDelete, "left");
jpMain.add(jlTitle, "left,wrap");
jpMain.add(jspLyrics, "span,grow");
setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
add(jpMain);
ObservationManager.register(this);
// force initial buttons states
updateButtonsState();
// Force initial message refresh
UtilFeatures.updateStatus(this);
}
/**
* Initializes the UI of edit lyrics mode.
*/
public void initEditUI() {
jtbEdit = new JajukToggleButton(IconLoader.getIcon(JajukIcons.EDIT));
jtbEdit.setToolTipText(Messages.getString("LyricsView.2"));
jtbEdit.addItemListener(new ItemListener() {
@Override
public void itemStateChanged(ItemEvent ev) {
if (jtbEdit.isSelected()) {
jtaLyrics.setEditable(true);
// Don't keep "No result found" text
if (jtaLyrics.getText().equals(Messages.getString("WikipediaView.3"))) {
jtaLyrics.getDocument().removeDocumentListener(LyricsView.this);
jtaLyrics.setText("");
jtaLyrics.getDocument().addDocumentListener(LyricsView.this);
}
jtaLyrics.requestFocus();
jtbEdit.setToolTipText(Messages.getString("LyricsView.3"));
} else {
exitEditLyrics(true);
jtbEdit.setToolTipText(Messages.getString("LyricsView.2"));
}
}
});
jbSave = new JajukButton(IconLoader.getIcon(JajukIcons.SAVE));
jbSave.setToolTipText(Messages.getString("LyricsView.4"));
jbSave.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
try {
JajukLyricsProvider provider = getJajukProvider();
LyricsService.commitLyrics(provider);
} catch (LyricsPersistenceException lpe) {
Log.error(lpe);
// Always the same i18n message : "Operation failed"
Messages.showErrorMessage(136, lpe.getMessage());
}
exitEditLyrics(false);
}
});
jbDelete = new JajukButton(IconLoader.getIcon(JajukIcons.DELETE));
jbDelete.setToolTipText(Messages.getString("LyricsView.5"));
jbDelete.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent ae) {
try {
JajukLyricsProvider provider = getJajukProvider();
LyricsService.deleteLyrics(provider);
} catch (LyricsPersistenceException lpe) {
Log.error(lpe);
Messages.showErrorMessage(136, lpe.getMessage());
}
exitEditLyrics(true);
}
});
}
/**
* Get the GUI provider.
*
* @return the jajuk provider
*/
public JajukLyricsProvider getJajukProvider() {
JajukLyricsProvider jajukLyricsProvider = new JajukLyricsProvider();
jajukLyricsProvider.setAudioFile(file);
jajukLyricsProvider.setLyrics(jtaLyrics.getText());
return jajukLyricsProvider;
}
/**
* Switch from lyrics edit to view mode.
*
* @param callUpdate Whether to call an update after switching
*/
public void exitEditLyrics(boolean callUpdate) {
changeDetected = false;
jtaLyrics.setEditable(false);
jtbEdit.setSelected(false);
updateButtonsState();
if (callUpdate) {
update(new JajukEvent(JajukEvents.FILE_LAUNCHED));
}
}
/*
* (non-Javadoc)
*
* @see org.jajuk.base.Observer#getRegistrationKeys()
*/
@Override
public Set<JajukEvents> getRegistrationKeys() {
final Set<JajukEvents> eventSubjectSet = new HashSet<JajukEvents>();
eventSubjectSet.add(JajukEvents.FILE_LAUNCHED);
eventSubjectSet.add(JajukEvents.ZERO);
eventSubjectSet.add(JajukEvents.PLAYER_STOP);
eventSubjectSet.add(JajukEvents.WEBRADIO_LAUNCHED);
eventSubjectSet.add(JajukEvents.LYRICS_DOWNLOADED);
return eventSubjectSet;
}
/*
* (non-Javadoc)
*
* @see org.jajuk.base.Observer#update(org.jajuk.base.Event)
*/
@Override
public void update(final JajukEvent event) {
// Ignore any event while we are editing
if (jtbEdit.isSelected()) {
return;
}
final JajukEvents subject = event.getSubject();
if (subject.equals(JajukEvents.FILE_LAUNCHED)) {
file = QueueModel.getPlayingFile();
// file is null is view started with no playing track (the event is
// simulated in initUI())
if (file == null) {
return;
}
// If Internet access is allowed, download lyrics
if (Conf.getBoolean(CONF_NETWORK_NONE_INTERNET_ACCESS)) {
resetNoInternet();
} else {
UtilGUI.showBusyLabel(this);
// Launch lyrics search asynchronously
new LyricsUpdateThread().start();
}
} else if (JajukEvents.ZERO.equals(subject) || JajukEvents.PLAYER_STOP.equals(subject)) {
reset();
file = null;
} else if (subject.equals(JajukEvents.WEBRADIO_LAUNCHED)) {
resetWebradio((WebRadio) event.getDetails().get(Const.DETAIL_CONTENT));
file = null;
} else if (subject.equals(JajukEvents.LYRICS_DOWNLOADED)
// file can be null if the search has been aborted in the mean time
&& file != null) {
refreshLyrics();
}
}
/**
* Reset GUI in case of Internet disabled.
*/
private void resetNoInternet() {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
removeAll();
add(jpMain);
jlTitle.setText(file.getTrack().getName());
jlTitle.setToolTipText(file.getTrack().getName());
updateButtonsState();
jtaLyrics.setText(Messages.getString("LyricsView.1"));
jspLyrics.setEnabled(true);
sURL = "<none>";
revalidate();
repaint();
}
});
}
/**
* Reset webradio.
*
* @param radio
*/
private void resetWebradio(final WebRadio radio) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
if (radio != null) {
jlTitle.setText(radio.getName());
updateButtonsState();
jspLyrics.setEnabled(false);
updateButtonsState();
revalidate();
repaint();
}
}
});
}
/**
* Compute buttons states.
*/
private void updateButtonsState() {
ILyricsProvider provider = LyricsService.getCurrentProvider();
// Delete button
jbDelete.setEnabled(file != null && provider != null
&& !(provider instanceof GenericWebLyricsProvider));
// Save button : enabled only for changes in the text area or
// if we just got lyrics from the web or form a txt file
// (so user can try to commit it to the tag)
jbSave.setEnabled((jtbEdit.isSelected() && changeDetected)
|| provider instanceof GenericWebLyricsProvider || provider instanceof TxtLyricsProvider);
}
/**
* Refresh lyrics once downloaded.
*/
private void refreshLyrics() {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
removeAll();
add(jpMain);
if ((lyrics != null) && (lyrics.length() > 0)) {
jtaLyrics.setText(lyrics);
} else {
jtaLyrics.setText(Messages.getString("WikipediaView.3"));
}
// Make sure to display the begin of the text (must be
// done in a thread to be executed when textarea display
// is actually finished)
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
jspLyrics.getVerticalScrollBar().setValue(0);
}
});
jlTitle.setText(file.getTrack().getName());
jlTitle.setToolTipText(sURL);
jspLyrics.setEnabled(true);
updateButtonsState();
revalidate();
repaint();
}
});
}
/**
* Hide lyrics scrollable text and display a "Ready to play" message.
*/
private void reset() {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
jspLyrics.setEnabled(false);
updateButtonsState();
jlTitle.setText(Messages.getString("JajukWindow.18"));
jtaLyrics.getDocument().removeDocumentListener(LyricsView.this);
jtaLyrics.setText("");
jtaLyrics.getDocument().addDocumentListener(LyricsView.this);
}
});
}
/*
* (non-Javadoc)
*
* @see org.jajuk.ui.IView#getDesc()
*/
@Override
public String getDesc() {
return Messages.getString("LyricsView.0");
}
/* (non-Javadoc)
* @see javax.swing.event.DocumentListener#removeUpdate(javax.swing.event.DocumentEvent)
*/
@Override
public void removeUpdate(DocumentEvent e) {
changeDetected = true;
updateButtonsState();
}
/* (non-Javadoc)
* @see javax.swing.event.DocumentListener#insertUpdate(javax.swing.event.DocumentEvent)
*/
@Override
public void insertUpdate(DocumentEvent e) {
changeDetected = true;
updateButtonsState();
}
/* (non-Javadoc)
* @see javax.swing.event.DocumentListener#changedUpdate(javax.swing.event.DocumentEvent)
*/
@Override
public void changedUpdate(DocumentEvent e) {
changeDetected = true;
updateButtonsState();
}
}