/* * 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 ext.FlowScrollPanel; import ext.services.lastfm.AlbumInfo; import ext.services.lastfm.AlbumListInfo; import ext.services.lastfm.ArtistInfo; import ext.services.lastfm.LastFmService; import ext.services.lastfm.SimilarArtistsInfo; import java.awt.FlowLayout; import java.awt.Graphics; import java.awt.Insets; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.net.URL; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import javax.swing.BoxLayout; import javax.swing.JEditorPane; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTabbedPane; import javax.swing.ScrollPaneConstants; import javax.swing.SwingUtilities; import javax.swing.SwingWorker; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import net.miginfocom.swing.MigLayout; import org.apache.commons.lang.StringUtils; import org.jajuk.base.Album; import org.jajuk.base.AlbumManager; import org.jajuk.base.File; import org.jajuk.events.JajukEvent; import org.jajuk.events.JajukEvents; import org.jajuk.events.ObservationManager; import org.jajuk.services.players.QueueModel; import org.jajuk.ui.perspectives.PerspectiveManager; import org.jajuk.ui.thumbnails.AbstractThumbnail; import org.jajuk.ui.thumbnails.LastFmAlbumThumbnail; import org.jajuk.ui.thumbnails.LastFmArtistThumbnail; import org.jajuk.ui.thumbnails.LocalAlbumThumbnail; import org.jajuk.ui.thumbnails.ThumbnailManager; import org.jajuk.util.Conf; import org.jajuk.util.Const; import org.jajuk.util.DownloadManager; import org.jajuk.util.Messages; import org.jajuk.util.UtilGUI; import org.jajuk.util.log.Log; import org.jdesktop.swingx.JXBusyLabel; /** * Show suggested albums based on current collection (bestof, novelties) and * LAstFM. */ @SuppressWarnings("serial") public class SuggestionView extends ViewAdapter { private JTabbedPane tabs; protected String artist; //Remove tab border, see // http://forum.java.sun.com/thread.jspa?threadID=260746&messageID=980405 class MyTabbedPaneUI extends javax.swing.plaf.basic.BasicTabbedPaneUI { @Override protected Insets getContentBorderInsets(int tabPlacement) { return new Insets(0, 0, 0, 0); } @Override protected void paintContentBorder(Graphics g, int tabPlacement, int selectedIndex) { // nothing to do here... } } enum SuggestionType { BEST_OF, NEWEST, RARE, OTHERS_ALBUMS, SIMILAR_ARTISTS } JScrollPane jpBestof; JScrollPane jpNewest; JScrollPane jpRare; JScrollPane jpOthersAlbums; JScrollPane jpSimilarArtists; private int comp = 0; List<Album> albumsNewest; List<Album> albumsPrefered; List<Album> albumsRare; /** Currently selected thumb. */ AbstractThumbnail selectedThumb; private AlbumListInfo albums; private SimilarArtistsInfo similar; JXBusyLabel busyLocal1 = new JXBusyLabel(); JXBusyLabel busyLocal2 = new JXBusyLabel(); JXBusyLabel busyLocal3 = new JXBusyLabel(); JXBusyLabel busyLastFM1 = new JXBusyLabel(); JXBusyLabel busyLastFM2 = new JXBusyLabel(); private class ThumbMouseListener extends MouseAdapter { @Override public void mousePressed(MouseEvent e) { AbstractThumbnail thumb = (AbstractThumbnail) ((JLabel) e.getSource()).getParent(); // remove red border on previous item if // different from this one if (selectedThumb != null && selectedThumb != thumb) { selectedThumb.setSelected(false); } // select the new selected thumb thumb.setSelected(true); selectedThumb = thumb; } } public SuggestionView() { super(); } @Override public String getDesc() { return Messages.getString("SuggestionView.0"); } @Override public void initUI() { tabs = new JTabbedPane(); // Now use the new TabbedPaneUI tabs.setUI(new MyTabbedPaneUI()); // Fill tabs with empty tabs tabs.addTab(Messages.getString("SuggestionView.1"), UtilGUI.getCentredPanel(new JLabel(Messages.getString("WikipediaView.3")))); tabs.addTab(Messages.getString("SuggestionView.2"), UtilGUI.getCentredPanel(new JLabel(Messages.getString("WikipediaView.3")))); tabs.addTab(Messages.getString("SuggestionView.5"), UtilGUI.getCentredPanel(new JLabel(Messages.getString("WikipediaView.3")))); tabs.addTab(Messages.getString("SuggestionView.3"), new JLabel(Messages.getString("SuggestionView.7"))); tabs.addTab(Messages.getString("SuggestionView.4"), new JLabel(Messages.getString("SuggestionView.7"))); addTabChangeListener(); selectTabFromConf(); refreshLocalCollectionTabs(); setLayout(new BoxLayout(this, BoxLayout.X_AXIS)); add(tabs); ObservationManager.register(this); } private void selectTabFromConf() { if (Conf.containsProperty(getClass().getName() + "_" + ((getPerspective() == null) ? "solo" : getPerspective().getID()))) { int index = Conf.getInt(getClass().getName() + "_" + ((getPerspective() == null) ? "solo" : getPerspective().getID())); if (index > 0 && index < tabs.getTabCount()) { tabs.setSelectedIndex(index); } } } private void addTabChangeListener() { // Refresh tabs on demand only, add changeListerner after tab creation to // avoid the stored tab to be overwriten at startup tabs.addChangeListener(new ChangeListener() { @Override public void stateChanged(ChangeEvent arg0) { refreshLastFMCollectionTabs(); // store the selected tab Conf.setProperty(getClass().getName() + "_" + ((getPerspective() == null) ? "solo" : getPerspective().getID()), Integer.toString(tabs.getSelectedIndex()).toString()); } }); } @Override public Set<JajukEvents> getRegistrationKeys() { Set<JajukEvents> eventSubjectSet = new HashSet<JajukEvents>(); eventSubjectSet.add(JajukEvents.FILE_LAUNCHED); eventSubjectSet.add(JajukEvents.PARAMETERS_CHANGE); eventSubjectSet.add(JajukEvents.COVER_DEFAULT_CHANGED); eventSubjectSet.add(JajukEvents.SUGGESTIONS_REFRESH); return eventSubjectSet; } /** * Refresh local thumbs. */ private void refreshLocalCollectionTabs() { // Display a busy panel in the mean-time // For some reasons, if we put that code into an invokeLater() call // it is executed after the next done() in next swing worker, no clue why // As a compromise, we only show busy label when called in EDT (not the case when the // call is from an update() ) if (SwingUtilities.isEventDispatchThread()) { busyLocal1.setBusy(true); busyLocal2.setBusy(true); busyLocal3.setBusy(true); // stop all existing busy labels before we add the new ones... //stopAllBusyLabels(); tabs.setComponentAt(0, UtilGUI.getCentredPanel(busyLocal1)); tabs.setComponentAt(1, UtilGUI.getCentredPanel(busyLocal2)); tabs.setComponentAt(2, UtilGUI.getCentredPanel(busyLocal3)); } SwingWorker<Void, Void> sw = new SwingWorker<Void, Void>() { JScrollPane jsp1; JScrollPane jsp2; JScrollPane jsp3; @Override public Void doInBackground() { albumsPrefered = AlbumManager.getInstance().getBestOfAlbums( Conf.getBoolean(Const.CONF_OPTIONS_HIDE_UNMOUNTED), NB_BESTOF_ALBUMS); albumsNewest = AlbumManager.getInstance().getNewestAlbums( Conf.getBoolean(Const.CONF_OPTIONS_HIDE_UNMOUNTED), NB_BESTOF_ALBUMS); albumsRare = AlbumManager.getInstance().getRarelyListenAlbums( Conf.getBoolean(Const.CONF_OPTIONS_HIDE_UNMOUNTED), NB_BESTOF_ALBUMS); refreshThumbsForLocalAlbums(); return null; } private void refreshThumbsForLocalAlbums() { // Refresh thumbs for required albums List<Album> albums = new ArrayList<Album>(10); albums.addAll(albumsPrefered); albums.addAll(albumsNewest); albums.addAll(albumsRare); if (albums.size() > 0) { for (Album album : albums) { // Try creating the thumbnail ThumbnailManager.refreshThumbnail(album, 100); } } } @Override public void done() { jsp1 = getLocalSuggestionsPanel(SuggestionType.BEST_OF); jsp2 = getLocalSuggestionsPanel(SuggestionType.NEWEST); jsp3 = getLocalSuggestionsPanel(SuggestionType.RARE); busyLocal1.setBusy(false); busyLocal2.setBusy(false); busyLocal3.setBusy(false); tabs.setComponentAt(0, jsp1); tabs.setComponentAt(1, jsp2); tabs.setComponentAt(2, jsp3); } }; sw.execute(); } /** * Refresh last fm collection tabs. * */ private void refreshLastFMCollectionTabs() { String newArtist = null; File current = QueueModel.getPlayingFile(); if (current != null) { newArtist = current.getTrack().getArtist().getName2(); } // if none track playing if (current == null // Last.FM infos is disable || !Conf.getBoolean(Const.CONF_LASTFM_INFO) // None internet access option is set || Conf.getBoolean(Const.CONF_NETWORK_NONE_INTERNET_ACCESS) // If unknown artist || (newArtist == null || newArtist.equals(Messages.getString(UNKNOWN_ARTIST)))) { // Set empty panels SwingUtilities.invokeLater(new Runnable() { @Override public void run() { tabs.setComponentAt(3, new JLabel(Messages.getString("SuggestionView.7"))); tabs.setComponentAt(4, new JLabel(Messages.getString("SuggestionView.7"))); } }); return; } // Check if artist changed, otherwise, just leave if (newArtist.equals(this.artist)) { return; } // Save current artist artist = newArtist; // Display a busy panel in the mean-time SwingUtilities.invokeLater(new Runnable() { @Override public void run() { busyLastFM1.setBusy(true); busyLastFM2.setBusy(true); tabs.setComponentAt(3, UtilGUI.getCentredPanel(busyLastFM1)); tabs.setComponentAt(4, UtilGUI.getCentredPanel(busyLastFM2)); } }); // Use a swing worker as construct takes a lot of time SwingWorker<Void, Void> sw = new SwingWorker<Void, Void>() { JScrollPane jsp1; JScrollPane jsp2; @Override public Void doInBackground() { try { // Fetch last.fm calls and downloads covers preFetchOthersAlbum(); preFetchSimilarArtists(); } catch (Exception e) { Log.error(e); } return null; } @Override public void done() { jsp1 = getLastFMSuggestionsPanel(SuggestionType.OTHERS_ALBUMS, false); jsp2 = getLastFMSuggestionsPanel(SuggestionType.SIMILAR_ARTISTS, false); busyLastFM1.setBusy(false); busyLastFM2.setBusy(false); tabs.setComponentAt(3, jsp1); tabs.setComponentAt(4, jsp2); } }; sw.execute(); } /** * Pre-load other album (done outside the EDT). * * @throws Exception the exception */ void preFetchOthersAlbum() throws Exception { albums = LastFmService.getInstance().getAlbumList(artist, true, 0); // Perform images downloads and caching if (albums != null && albums.getAlbums().size() > 0) { for (AlbumInfo album : albums.getAlbums()) { // stop this list of albums if there was another file launched in the meantime String albumUrl = album.getBigCoverURL(); if (StringUtils.isBlank(albumUrl)) { continue; } // Download thumb URL remote = new URL(albumUrl); // Download image and store file reference (to generate the // popup thumb for ie) DownloadManager.downloadToCache(remote); } } } /** * Pre-load other album (done outside the EDT). * * @throws Exception the exception */ void preFetchSimilarArtists() throws Exception { // Perform last.fm calls similar = LastFmService.getInstance().getSimilarArtists(artist); // artists is null for void (unknown) similar artists if (similar != null && similar.getArtists() != null) { List<ArtistInfo> artists = similar.getArtists(); for (ArtistInfo similarArtist : artists) { // stop this list of albums if there was another file launched in the meantime, another refresh will take place anyway String artistUrl = similarArtist.getImageUrl(); if (StringUtils.isBlank(artistUrl)) { continue; } // Download thumb URL remote = new URL(artistUrl); // Download the picture and store file reference (to // generate the popup thumb for ie) DownloadManager.downloadToCache(remote); } } } /** * Return the result panel for local albums. * * @param type * * @return the local suggestions panel */ JScrollPane getLocalSuggestionsPanel(SuggestionType type) { FlowScrollPanel out = new FlowScrollPanel(); out.setLayout(new FlowLayout(FlowLayout.LEFT)); JScrollPane jsp = new JScrollPane(out, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); jsp.setBorder(null); out.setScroller(jsp); List<Album> albums = null; if (type == SuggestionType.BEST_OF) { albums = albumsPrefered; } else if (type == SuggestionType.NEWEST) { albums = albumsNewest; } else if (type == SuggestionType.RARE) { albums = albumsRare; } if (albums != null && albums.size() > 0) { for (Album album : albums) { LocalAlbumThumbnail thumb = new LocalAlbumThumbnail(album, 100, false); thumb.populate(); thumb.getIcon().addMouseListener(new ThumbMouseListener()); out.add(thumb); } } else { out.add(UtilGUI.getCentredPanel(new JLabel(Messages.getString("WikipediaView.3")))); } return jsp; } /** * Return the result panel for lastFM information. * * @param type * @param artistView * * @return the last fm suggestions panel */ JScrollPane getLastFMSuggestionsPanel(SuggestionType type, boolean artistView) { FlowScrollPanel flowPanel = new FlowScrollPanel(); JScrollPane jsp = new JScrollPane(flowPanel, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); jsp.setBorder(null); flowPanel.setScroller(jsp); flowPanel.setLayout(new FlowLayout(FlowLayout.CENTER)); if (type == SuggestionType.OTHERS_ALBUMS) { if (albums != null && albums.getAlbums().size() > 0) { for (AlbumInfo album : albums.getAlbums()) { AbstractThumbnail thumb = new LastFmAlbumThumbnail(album); thumb.setArtistView(artistView); thumb.populate(); if (thumb.getIcon() != null) { thumb.getIcon().addMouseListener(new ThumbMouseListener()); flowPanel.add(thumb); } } } // No result found else { return new JScrollPane(getNothingFoundPanel()); } } else if (type == SuggestionType.SIMILAR_ARTISTS) { if (similar != null) { List<ArtistInfo> artists = similar.getArtists(); for (ArtistInfo similarArtist : artists) { AbstractThumbnail thumb = new LastFmArtistThumbnail(similarArtist); thumb.setArtistView(artistView); thumb.populate(); if (thumb.getIcon() != null) { thumb.getIcon().addMouseListener(new ThumbMouseListener()); flowPanel.add(thumb); } } } // No result found else { return new JScrollPane(getNothingFoundPanel()); } } return jsp; } @Override public void update(JajukEvent event) { JajukEvents subject = event.getSubject(); if (subject.equals(JajukEvents.FILE_LAUNCHED)) { // Change local collection suggestions every 10 track plays if (comp % 10 == 0) { refreshLocalCollectionTabs(); } comp++; // update last.fm panels refreshLastFMCollectionTabs(); } else if (subject.equals(JajukEvents.PARAMETERS_CHANGE) && isLastFMTabsVisible()) { // The show/hide unmounted may have changed, refresh local // collection panels refreshLastFMCollectionTabs(); } else if (subject.equals(JajukEvents.COVER_DEFAULT_CHANGED) || subject.equals(JajukEvents.SUGGESTIONS_REFRESH)) { // New default cover, refresh the view refreshLocalCollectionTabs(); } } /** * [Perf]. * * @return whether LastFM tabs are visible or not */ private boolean isLastFMTabsVisible() { // Refresh artists only if user selected similar artists or albums tab return (tabs.getSelectedIndex() == 3 || tabs.getSelectedIndex() == 4) // Check this view perspective is visible && PerspectiveManager.getCurrentPerspective().equals(this.getPerspective()); } /** * Refresh lastFM tabs on perspective selection if tabs visible. */ @Override public void onPerspectiveSelection() { refreshLastFMCollectionTabs(); } /** * Gets the nothing found panel. * * @return a panel with text explaining why no item has been found */ JPanel getNothingFoundPanel() { JPanel out = new JPanel(new MigLayout("ins 5", "grow")); JEditorPane jteNothing = new JEditorPane("text/html", Messages.getString("SuggestionView.7")); jteNothing.setBorder(null); jteNothing.setEditable(false); jteNothing.setOpaque(false); jteNothing.setToolTipText(Messages.getString("SuggestionView.7")); out.add(jteNothing, "center,grow"); return out; } }