/* * 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.thumbnails; import com.vlsolutions.swing.docking.ShadowBorder; import java.awt.Dimension; import java.awt.Point; import java.awt.Rectangle; import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.Transferable; import java.awt.datatransfer.UnsupportedFlavorException; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseEvent; import java.awt.event.MouseMotionAdapter; import java.io.File; import java.io.IOException; import java.util.List; import javax.swing.BorderFactory; import javax.swing.JComponent; import javax.swing.JDialog; import javax.swing.JLabel; import javax.swing.JMenuItem; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.Timer; import javax.swing.TransferHandler; import javax.swing.WindowConstants; import org.jajuk.base.Item; import org.jajuk.base.Track; import org.jajuk.base.TrackManager; import org.jajuk.ui.actions.ActionManager; import org.jajuk.ui.actions.JajukAction; import org.jajuk.ui.actions.JajukActions; import org.jajuk.ui.helpers.JajukMouseAdapter; import org.jajuk.ui.views.CoverView; import org.jajuk.ui.windows.JajukMainWindow; 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.UtilGUI; import org.jajuk.util.UtilSystem; import org.jajuk.util.log.Log; /** * Album thumb represented as album cover + (optionally) others text information * and some features like dnd, menu item to play, search cover, album popup * display... */ public abstract class AbstractThumbnail extends JPanel implements ActionListener, Transferable { /** Generated serialVersionUID. */ private static final long serialVersionUID = -6396225563540281695L; /** Size. */ int size; protected JLabel jlIcon; private static long lDateLastMove; private static Point lastPosition; JPopupMenu jmenu; JMenuItem jmiPlay; JMenuItem jmiPush; JMenuItem jmiFrontPush; JMenuItem jmiDelete; JMenuItem jmiPlayShuffle; JMenuItem jmiPlayRepeat; JMenuItem jmiGetCovers; private JMenuItem jmiShowPopup; JMenuItem jmiCDDBWizard; JMenuItem jmiProperties; JMenuItem jmiOpenLastFMSite; /** Dragging flag used to disable simple click behavior. */ private static boolean bDragging = false; /** Current details dialog. */ private static ThumbnailPopup details; private static AbstractThumbnail last; private static AbstractThumbnail mouseOverItem = null; /** Whether this thumb is used in artist view *. */ private boolean artistView; /** Associated file. */ File fCover; /** Timer used to launch popup */ static { Timer timerPopup = new Timer(200, new ActionListener() { @Override public void actionPerformed(ActionEvent arg0) { try { // Close popup ASAP when over none catalog item if (mouseOverItem == null) { if (details != null) { details.dispose(); details = null; } last = null; // display a popup after n seconds only if item changed } else if ((System.currentTimeMillis() - lDateLastMove >= 700) && mouseOverItem != last && !bDragging) { // Store current item last = mouseOverItem; // Finally display the popup (Leave if user unselected // the option "Show catalog popups" if (Conf.getBoolean(Const.CONF_SHOW_POPUPS)) { mouseOverItem.displayPopup(true); } } bDragging = false; } catch (Exception e) { // Make sure not to exit Log.error(e); } } }); timerPopup.start(); } /** * Constructor. * * @param size : * size of the thumbnail */ protected AbstractThumbnail(int size) { this.size = size; setSelected(false); } /** * Checks if is artist view. * * @return true, if is artist view */ protected boolean isArtistView() { return artistView; } /** * Sets the artist view. * * @param artistBioThumb the new artist view */ public void setArtistView(boolean artistBioThumb) { this.artistView = artistBioThumb; } /** * Display a popup over the catalog item. * @param autoclose should the popup close by itself or stuck on the screen ? */ private void displayPopup(boolean autoclose) { // close popup if any visible if (details != null) { details.closeIfAutoclose(); details = null; } // don't show details if the contextual popup menu // is visible if (jmenu.isVisible()) { return; } String description = getDescription(); if (description != null) { details = new ThumbnailPopup(description, new Rectangle(jlIcon.getLocationOnScreen(), new Dimension(jlIcon.getWidth(), jlIcon.getHeight())), autoclose); } } /** * Populate. */ public abstract void populate(); /** * Return HTML text to display in the popup. * * @return the description */ public abstract String getDescription(); /** * Performs common UI operations for any kind of thumb. */ void postPopulate() { // do this only once as it might be a costly operation... Item item = getItem(); // Album menu jmenu = new JPopupMenu(); jmiPlay = new JMenuItem(ActionManager.getAction(JajukActions.PLAY_SELECTION)); jmiPlay.putClientProperty(Const.DETAIL_SELECTION, item); jmiPush = new JMenuItem(ActionManager.getAction(JajukActions.PUSH_SELECTION)); jmiPush.putClientProperty(Const.DETAIL_SELECTION, item); jmiFrontPush = new JMenuItem(ActionManager.getAction(JajukActions.PUSH_FRONT_SELECTION)); jmiFrontPush.putClientProperty(Const.DETAIL_SELECTION, item); jmiDelete = new JMenuItem(ActionManager.getAction(JajukActions.DELETE)); jmiDelete.putClientProperty(Const.DETAIL_SELECTION, item); jmiPlayShuffle = new JMenuItem(ActionManager.getAction(JajukActions.PLAY_SHUFFLE_SELECTION)); jmiPlayShuffle.putClientProperty(Const.DETAIL_SELECTION, item); jmiPlayRepeat = new JMenuItem(ActionManager.getAction(JajukActions.PLAY_REPEAT_SELECTION)); jmiPlayRepeat.putClientProperty(Const.DETAIL_SELECTION, item); jmiGetCovers = new JMenuItem(Messages.getString("CatalogView.7"), IconLoader.getIcon(JajukIcons.COVER_16X16)); jmiGetCovers.addActionListener(this); jmiShowPopup = new JMenuItem(Messages.getString("CatalogView.20"), IconLoader.getIcon(JajukIcons.POPUP)); jmiShowPopup.addActionListener(this); jmiCDDBWizard = new JMenuItem(ActionManager.getAction(JajukActions.CDDB_SELECTION)); jmiCDDBWizard.putClientProperty(Const.DETAIL_SELECTION, item); jmiProperties = new JMenuItem(ActionManager.getAction(JajukActions.SHOW_PROPERTIES)); jmiProperties.putClientProperty(Const.DETAIL_SELECTION, item); if (UtilSystem.isBrowserSupported()) { JajukAction actionOpenLastFM = ActionManager.getAction(JajukActions.LAUNCH_IN_BROWSER); // Change action label jmiOpenLastFMSite = new JMenuItem(actionOpenLastFM); jmiOpenLastFMSite.setText(Messages.getString("AbstractThumbnail.0")); jmiOpenLastFMSite.setToolTipText(Messages.getString("AbstractThumbnail.0")); } // We add all menu items, each implementation of this class should hide // (setVisible(false)) menu items that are not available in their // context jmenu.add(jmiPlay); jmenu.add(jmiFrontPush); jmenu.add(jmiPush); jmenu.add(jmiPlayShuffle); jmenu.add(jmiPlayRepeat); jmenu.addSeparator(); jmenu.add(jmiDelete); jmenu.addSeparator(); jmenu.add(jmiCDDBWizard); jmenu.add(jmiGetCovers); jmenu.add(jmiShowPopup); if (UtilSystem.isBrowserSupported()) { jmenu.add(jmiOpenLastFMSite); } jmenu.addSeparator(); jmenu.add(jmiProperties); jlIcon.addMouseMotionListener(new MouseMotionAdapter() { @Override public void mouseDragged(MouseEvent e) { // Notify the mouse listener that we are dragging bDragging = true; JComponent c = (JComponent) e.getSource(); TransferHandler handler = c.getTransferHandler(); handler.exportAsDrag(c, e, TransferHandler.COPY); } @Override public void mouseMoved(MouseEvent e) { super.mouseMoved(e); lDateLastMove = System.currentTimeMillis(); lastPosition = e.getPoint(); } }); jlIcon.addMouseListener(new JajukMouseAdapter() { @Override public void handlePopup(MouseEvent e) { if (e.getSource() == jlIcon) { // Show contextual menu jmenu.show(jlIcon, e.getX(), e.getY()); // Hide any details frame if (details != null) { details.dispose(); details = null; } } } @Override public void handleActionSeveralClicks(MouseEvent e) { launch(); } @Override public void mousePressed(MouseEvent e) { if (bDragging) { return; } super.mousePressed(e); } @Override public void mouseEntered(MouseEvent e) { mouseOverItem = AbstractThumbnail.this; } @Override public void mouseExited(MouseEvent e) { // Consider an exit only if mouse really moved to avoid // closing popup when popup appears over the mouse cursor // (then, a mouseExit event is thrown) if (!e.getPoint().equals(lastPosition) && // Don't close popup if user is still over it !(details != null && details.contains(e.getPoint()))) { mouseOverItem = null; } } @Override public void mouseReleased(MouseEvent e) { // Leave if already dragging if (bDragging) { return; } super.mouseReleased(e); } }); } /** * Sets the selected. * * @param b */ public final void setSelected(boolean b) { requestFocusInWindow(); // Add a shadow for selected items if (b) { setBorder(new ShadowBorder(false)); } else { // add an empty border of the same size than the border to avoid // image moves when setting borders setBorder(BorderFactory.createEmptyBorder(1, 1, 5, 5)); } } /** * Launch. */ public abstract void launch(); /** * If the thumb represents something (album, artist...) known in the * collection, the implementation of this method should return the associated * item * * @return the collection item */ public abstract Item getItem(); /* * (non-Javadoc) * * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent) */ @Override public void actionPerformed(ActionEvent e) { if (e.getSource() == jmiGetCovers) { // This item is enabled only for albums JDialog jd = new JDialog(JajukMainWindow.getInstance(), Messages.getString("CatalogView.18")); org.jajuk.base.File file = null; // We sort associated tracks because we want to analyze the first file of the set // as it is more likely to contain global cover tag. List<Track> tracks = TrackManager.getInstance().getAssociatedTracks(getItem(), true); if (tracks.size() > 0) { // Take first track found Track track = tracks.iterator().next(); file = track.getBestFile(false); } CoverView cv = null; if (file != null) { cv = new CoverView(file, jd); cv.setID("catalog/0"); cv.initUI(); jd.add(cv); jd.setSize(600, 450); // Keep it unresizable to keep things simple with cover view // resizing issues, see @CoverView.CoverResetThread comments. jd.setResizable(false); UtilGUI.centerWindow(jd); jd.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); jd.setVisible(true); } else { Messages.showErrorMessage(166); } } else if (e.getSource() == jmiShowPopup) { displayPopup(false); } } /* * (non-Javadoc) * * @see java.awt.datatransfer.Transferable#getTransferData(java.awt.datatransfer.DataFlavor) */ @Override public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException { return null; } /* * (non-Javadoc) * * @see java.awt.datatransfer.Transferable#getTransferDataFlavors() */ @Override public DataFlavor[] getTransferDataFlavors() { return null; } /* * (non-Javadoc) * * @see java.awt.datatransfer.Transferable#isDataFlavorSupported(java.awt.datatransfer.DataFlavor) */ @Override public boolean isDataFlavorSupported(DataFlavor flavor) { return false; } /** * Gets the icon. * * @return the icon */ public JLabel getIcon() { return jlIcon; } }