/* * ThumbsTopComponent.java * * Copyright (C) 2006-2014 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.thumbviewer; import com.ebixio.virtmus.CommonExplorers; import com.ebixio.virtmus.DraggableThumbnail; import com.ebixio.virtmus.MusicPage; import com.ebixio.virtmus.MusicPageNode; import com.ebixio.virtmus.Song; import com.ebixio.virtmus.SongNode; import com.ebixio.virtmus.Thumbnail; import java.awt.Component; import java.awt.Container; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.Insets; import java.awt.Rectangle; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.beans.PropertyVetoException; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import net.java.swingfx.jdraggable.DragPolicy; import net.java.swingfx.jdraggable.DraggableManager; import org.openide.ErrorManager; import org.openide.nodes.Node; import org.openide.util.Exceptions; import org.openide.util.ImageUtilities; import org.openide.util.Lookup; import org.openide.util.LookupEvent; import org.openide.util.LookupListener; import org.openide.util.NbBundle; import org.openide.util.Utilities; import org.openide.windows.TopComponent; import org.openide.windows.WindowManager; /** * Top component which displays something. */ final class ThumbsTopComponent extends TopComponent implements LookupListener, MouseListener { private static ThumbsTopComponent instance; /** path to the icon used by the component and its open action */ static final String ICON_PATH = "com/ebixio/thumbviewer/image-x-generic.png"; private static final String PREFERRED_ID = "ThumbsTopComponent"; private Song loadedSong = null; private DraggableManager draggableManager; private int hgap = 25, vgap = 25; private SongNode songNode = null; final Lookup.Result<SongNode> lookupSongs; final Lookup.Result<MusicPageNode> lookupPages; /** * We want to know when the song pages have been marked "dirty" so we can update * the thumbnail and set the proper one to "selected" */ private ChangeListener changeListener = new ChangeListener() { @Override public void stateChanged(ChangeEvent e) { if (e.getSource().getClass() == Song.class) { MusicPage selectedPage = null; Song s = (Song)e.getSource(); for (Component c: jPanel.getComponents()) { Thumbnail t = (Thumbnail)c; if (t.isSelected()) { // If the page has changed (the page annotations or name) // this thumbnail might have been discarded by the page. selectedPage = t.getPage(); break; } } loadSong(s); if (selectedPage != null) selectedPage.getThumbnail().setSelected(true); } } }; private ThumbsTopComponent() { initComponents(); setName(NbBundle.getMessage(ThumbsTopComponent.class, "CTL_ThumbsTopComponent")); setToolTipText(NbBundle.getMessage(ThumbsTopComponent.class, "HINT_ThumbsTopComponent")); setIcon(ImageUtilities.loadImage(ICON_PATH, true)); draggableManager = new ThumbnailDraggableManager(jPanel); draggableManager.setDragPolicy(DragPolicy.STRICT); // 1 wheel scroll = 3 clicks on the scoll bar arrow jScrollPane.getVerticalScrollBar().setUnitIncrement((MusicPage.thumbH + vgap) / 3); layoutThumbs(); lookupSongs = Utilities.actionsGlobalContext().lookupResult(SongNode.class); lookupPages = Utilities.actionsGlobalContext().lookupResult(MusicPageNode.class); lookupSongs.addLookupListener(this); lookupPages.addLookupListener(this); } private void layoutThumbs() { jPanel.setLayout(new ModifiedFlowLayout(FlowLayout.CENTER, hgap, vgap)); jPanel.validate(); // Forces the component to re-layout subcomponents. } /** This method is called from within the constructor to * initialize the form. * WARNING: Do NOT modify this code. The content of this method is * always regenerated by the Form Editor. */ // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents private void initComponents() { jScrollPane = new javax.swing.JScrollPane(); jPanel = new javax.swing.JPanel(); addComponentListener(new java.awt.event.ComponentAdapter() { public void componentResized(java.awt.event.ComponentEvent evt) { formComponentResized(evt); } }); jScrollPane.setBackground(new java.awt.Color(255, 255, 255)); jScrollPane.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); jScrollPane.setViewportBorder(javax.swing.BorderFactory.createEmptyBorder(10, 10, 10, 10)); jPanel.setBackground(new java.awt.Color(255, 255, 255)); javax.swing.GroupLayout jPanelLayout = new javax.swing.GroupLayout(jPanel); jPanel.setLayout(jPanelLayout); jPanelLayout.setHorizontalGroup( jPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGap(0, 398, Short.MAX_VALUE) ); jPanelLayout.setVerticalGroup( jPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGap(0, 533, Short.MAX_VALUE) ); jScrollPane.setViewportView(jPanel); javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(jScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 410, Short.MAX_VALUE) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(jScrollPane, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, 553, Short.MAX_VALUE) ); }// </editor-fold>//GEN-END:initComponents private void formComponentResized(java.awt.event.ComponentEvent evt) {//GEN-FIRST:event_formComponentResized layoutThumbs(); }//GEN-LAST:event_formComponentResized // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JPanel jPanel; private javax.swing.JScrollPane jScrollPane; // End of variables declaration//GEN-END:variables /** * Gets default instance. Do not use directly: reserved for *.settings files only, * i.e. deserialization routines; otherwise you could get a non-deserialized instance. * To obtain the singleton instance, use {@link findInstance}. */ public static synchronized ThumbsTopComponent getDefault() { if (instance == null) { instance = new ThumbsTopComponent(); } return instance; } /** * Obtain the ThumbsTopComponent instance. Never call {@link #getDefault} directly! */ public static synchronized ThumbsTopComponent findInstance() { TopComponent win = WindowManager.getDefault().findTopComponent(PREFERRED_ID); if (win == null) { ErrorManager.getDefault().log(ErrorManager.WARNING, "Cannot find MyWindow component. It will not be located properly in the window system."); return getDefault(); } if (win instanceof ThumbsTopComponent) { return (ThumbsTopComponent)win; } ErrorManager.getDefault().log(ErrorManager.WARNING, "There seem to be multiple components with the '" + PREFERRED_ID + "' ID. That is a potential source of errors and unexpected behavior."); return getDefault(); } @Override public int getPersistenceType() { return TopComponent.PERSISTENCE_ALWAYS; } @Override public void resultChanged(LookupEvent ev) { Collection<? extends SongNode> sNodes = lookupSongs.allInstances(); Collection<? extends MusicPageNode> mNodes = lookupPages.allInstances(); SongNode sNode = null; MusicPageNode mNode = null; if (!sNodes.isEmpty()) { sNode = sNodes.iterator().next(); } else if (!mNodes.isEmpty()) { mNode = mNodes.iterator().next(); if (mNode.getParentNode() instanceof SongNode) { sNode = (SongNode) mNode.getParentNode(); } } if (sNode != null) { songNode = sNode; Song s = songNode.getSong(); if (loadedSong != s) this.loadSong(s); } if (mNode != null) { MusicPage mp = mNode.getPage(); for (MusicPage m : loadedSong.pageOrder) { if (m == mp) { m.getThumbnail().setSelected(true); } else { m.getThumbnail().setSelected(false); } } } } /** replaces this in object stream */ @Override public Object writeReplace() { return new ResolvableHelper(); } @Override protected String preferredID() { return PREFERRED_ID; } // <editor-fold defaultstate="collapsed" desc=" MouseListener interface "> @Override public void mouseClicked(MouseEvent e) { if (e.getClickCount() == 2 || e.getButton() != MouseEvent.BUTTON1) { Thumbnail t = (Thumbnail)e.getComponent(); try { if (songNode == null) return; MusicPage mp = t.getPage(); for (Node n: songNode.getChildren().getNodes()) { if (n instanceof MusicPageNode) { MusicPageNode mpn = (MusicPageNode) n; if (mpn.getPage() == mp) { CommonExplorers.MainExplorerManager.setSelectedNodes(new Node[]{mpn}); } } } for (MusicPage m : loadedSong.pageOrder) { m.getThumbnail().setSelected(m == mp); } } catch (PropertyVetoException ex) { Exceptions.printStackTrace(ex); } } } @Override public void mousePressed(MouseEvent e) { if (e.getButton() == MouseEvent.BUTTON1) { Thumbnail t = (Thumbnail)e.getComponent(); // Make this a key listener, and if ctrl/shift is not held down deselect all others for (Component c: jPanel.getComponents()) { ((Thumbnail)c).setSelected(false); } t.setSelected(true); } } @Override public void mouseReleased(MouseEvent e) { } @Override public void mouseEntered(MouseEvent e) { } @Override public void mouseExited(MouseEvent e) { } // </editor-fold> final static class ResolvableHelper implements Serializable { private static final long serialVersionUID = 1L; public Object readResolve() { return ThumbsTopComponent.getDefault(); } } public void loadSong(Song s) { if (loadedSong != null) loadedSong.removeChangeListener(changeListener); for (Component c: jPanel.getComponents()) { //c.removeMouseListener(this); // OR for (MouseListener m: c.getMouseListeners()) { c.removeMouseListener(m); } } jPanel.removeAll(); jPanel.setSize(this.getWidth(), this.getHeight()); if (s == null) return; s.addChangeListener(changeListener); loadedSong = s; for (MusicPage p: loadedSong.pageOrder) { DraggableThumbnail t = p.getThumbnail(); t.addMouseListener(this); t.setSelected(false); jPanel.add(t); } } void reorderThumbs() { ArrayList<MusicPage> newOrder = new ArrayList<>(); Thumbnail selectedThumb = null, otherThumb; int components = jPanel.getComponentCount(); int selectedIdx = 0, insertBefore = components; boolean changed = false; // Find the thumbnail we want to move for (int i = 0; i < components; i++) { Thumbnail t = (Thumbnail)jPanel.getComponent(i); if ( t.isSelected() ) { selectedThumb = t; selectedIdx = i; break; } } if (selectedThumb == null) return; // Find where we want to move it Rectangle prevDrop = new Rectangle(0, 0, -1, -1); for (int i = 0; i < components; i++) { if (i == selectedIdx) continue; otherThumb = (Thumbnail)jPanel.getComponent(i); Rectangle rect = otherThumb.getBounds(); Rectangle drop = new Rectangle(0, 0, rect.x, rect.y + (rect.height + vgap) / 2); // Check to see if it was dropped at the end of the previous row if (drop.height > prevDrop.height) { prevDrop.width = jPanel.getWidth(); if (prevDrop.contains(selectedThumb.getLocation())) { insertBefore = i; break; } } if (drop.contains(selectedThumb.getLocation())) { insertBefore = i; break; } else { prevDrop = drop; } } // Move it to the new location and reload the thumbnails if (insertBefore != selectedIdx) { for (int i = 0; i < insertBefore; i++) { if (i != selectedIdx) newOrder.add(loadedSong.pageOrder.get(i)); } newOrder.add(loadedSong.pageOrder.get(selectedIdx)); for (int i = insertBefore; i < loadedSong.pageOrder.size(); i++) { if (i != selectedIdx) newOrder.add(loadedSong.pageOrder.get(i)); } // The two better have the same number of pages if (loadedSong.pageOrder.size() != newOrder.size()) return; for (int i = 0; i < newOrder.size(); i++) { if (loadedSong.pageOrder.get(i) != newOrder.get(i)) { // We have actually changed the order changed = true; break; } } if (changed) { loadedSong.pageOrder.clear(); loadedSong.pageOrder.addAll(newOrder); loadedSong.setDirty(true); loadedSong.notifyListeners(); loadSong(loadedSong); } } layoutThumbs(); //ExplorerManager manager = com.ebixio.virtmus.MainApp.findInstance().getExplorerManager(); //manager.setRootContext(new AbstractNode(new PlayLists())); } // <editor-fold defaultstate="collapsed" desc=" ModifiedFlowLayout "> /** * A modified version of FlowLayout that allows containers using this * Layout to behave in a reasonable manner when placed inside a * JScrollPane * * @author Babu Kalakrishnan */ public class ModifiedFlowLayout extends FlowLayout { public ModifiedFlowLayout() { super(); } public ModifiedFlowLayout(int align) { super(align); } public ModifiedFlowLayout(int align, int hgap, int vgap) { super(align, hgap, vgap); } @Override public Dimension minimumLayoutSize(Container target) { return computeSize(target, true); } @Override public Dimension preferredLayoutSize(Container target) { return computeSize(target, false); } private Dimension computeSize(Container target, boolean minimum) { synchronized (target.getTreeLock()) { int hgap = getHgap(); int vgap = getVgap(); int w = target.getWidth(); // Let this behave like a regular FlowLayout (single row) // if the container hasn't been assigned any size yet if (w == 0) w = Integer.MAX_VALUE; Insets insets = target.getInsets(); if (insets == null) insets = new Insets(0, 0, 0, 0); int reqdWidth = 0; int maxwidth = w - (insets.left + insets.right + hgap * 2); int n = target.getComponentCount(); int x = 0; int y = insets.top; int rowHeight = 0; for (int i = 0; i < n; i++) { Component c = target.getComponent(i); if (c.isVisible()) { Dimension d = minimum ? c.getMinimumSize() : c.getPreferredSize(); if ((x == 0) || ((x + d.width) <= maxwidth)) { if (x > 0) { x += hgap; } x += d.width; rowHeight = Math.max(rowHeight, d.height); } else { x = d.width; y += vgap + rowHeight; rowHeight = d.height; } reqdWidth = Math.max(reqdWidth, x); } } y += rowHeight + vgap * 2; return new Dimension(reqdWidth+insets.left+insets.right, y+insets.bottom); } } } //</editor-fold> }