001    /*
002     * $Id: JXMultiThumbSlider.java,v 1.6 2006/05/10 01:10:28 joshy Exp $
003     *
004     * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
005     * Santa Clara, California 95054, U.S.A. All rights reserved.
006     *
007     * This library is free software; you can redistribute it and/or
008     * modify it under the terms of the GNU Lesser General Public
009     * License as published by the Free Software Foundation; either
010     * version 2.1 of the License, or (at your option) any later version.
011     * 
012     * This library is distributed in the hope that it will be useful,
013     * but WITHOUT ANY WARRANTY; without even the implied warranty of
014     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
015     * Lesser General Public License for more details.
016     * 
017     * You should have received a copy of the GNU Lesser General Public
018     * License along with this library; if not, write to the Free Software
019     * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
020     */
021    
022    package org.jdesktop.swingx;
023    import java.awt.Color;
024    import java.awt.Dimension;
025    import java.awt.Graphics;
026    import java.awt.Graphics2D;
027    import java.awt.Point;
028    import java.awt.event.ActionEvent;
029    import java.awt.event.ActionListener;
030    import java.awt.event.MouseEvent;
031    import java.util.ArrayList;
032    import java.util.Comparator;
033    import java.util.List;
034    import javax.swing.JComponent;
035    import javax.swing.UIManager;
036    import javax.swing.event.MouseInputAdapter;
037    import org.jdesktop.swingx.multislider.*;
038    import org.jdesktop.swingx.multislider.ThumbListener;
039    import org.jdesktop.swingx.multislider.ThumbRenderer;
040    import org.jdesktop.swingx.multislider.TrackRenderer;
041    import org.jdesktop.swingx.plaf.JXMultiThumbSliderAddon;
042    import org.jdesktop.swingx.plaf.LookAndFeelAddons;
043    import org.jdesktop.swingx.plaf.MultiThumbSliderUI;
044    
045    /**
046     * <p>A slider which can have multiple control points or <i>Thumbs</i></p>
047     * <p>The thumbs each represent a value between the minimum and maximum values
048     * of the slider.  Thumbs can pass each other when being dragged.  Thumbs have
049     * no default visual representation. To customize the look of the thumbs and the
050     * track behind the thumbs you must provide a ThumbRenderer and a TrackRenderer 
051     * implementation. To listen for changes to the thumbs you must provide an 
052     * implemention of ThumbDataListener.
053     * 
054     * TODOs:
055     * move public inner classes (interfaces, etc) to subpackage
056     * add min/maxvalue convenience methods to jxmultithumbslider
057     * add plafs for windows, mac, and basic (if necessary)
058     * make way to properly control the height.
059     * hide the inner thumb component
060     *
061     * @author joshy
062     */
063    public class JXMultiThumbSlider<E> extends JComponent {
064        static {
065            LookAndFeelAddons.contribute(new JXMultiThumbSliderAddon());
066        }
067    
068        public static final String uiClassID = "MultiThumbSliderUI";
069        
070        /** Creates a new instance of JMultiThumbSlider */
071        public JXMultiThumbSlider() {
072            thumbs = new ArrayList();
073            setLayout(null);
074            
075        tdl = new ThumbHandler();
076            
077            setModel(new DefaultMultiThumbModel<E>());
078            MultiThumbMouseListener mia = new MultiThumbMouseListener(this);
079            addMouseListener(mia);
080            addMouseMotionListener(mia);
081            
082            Dimension dim = new Dimension(60,16);
083            setPreferredSize(dim);
084            setSize(dim);
085            setMinimumSize(new Dimension(30,16));
086            updateUI();
087        }
088        
089        public MultiThumbSliderUI getUI() {
090            return (MultiThumbSliderUI)ui;
091        }
092        
093        public void setUI(MultiThumbSliderUI ui) {
094            super.setUI(ui);
095        }
096        
097        public void updateUI() {
098            setUI((MultiThumbSliderUI)UIManager.getUI(this));
099            invalidate();
100        }
101        
102        /**
103         * @inheritDoc
104         */
105        @Override
106        public String getUIClassID() {
107            return uiClassID;
108        }
109    
110        private ThumbDataListener tdl;
111        
112        private List<ThumbComp> thumbs;
113        
114        private ThumbRenderer thumbRenderer;
115        
116        private TrackRenderer trackRenderer;
117        
118        private MultiThumbModel<E> model;
119    
120        private List<ThumbListener> listeners = new ArrayList();
121            
122        private ThumbComp selected;
123        
124        protected void paintComponent(Graphics g) {
125            if(isVisible()) {
126                if(trackRenderer != null) {
127                    JComponent comp = trackRenderer.getRendererComponent(this);
128                    comp.paint(g);
129                } else {
130                    paintRange((Graphics2D)g);
131                }
132            }
133        }
134    
135        private void paintRange(Graphics2D g) {
136            g.setColor(Color.blue);
137            g.fillRect(0,0,getWidth(),getHeight());
138        }    
139        
140        private float getThumbValue(int thumbIndex) {
141            return getModel().getThumbAt(thumbIndex).getPosition();
142        }
143        
144        private float getThumbValue(ThumbComp thumb) {
145            return getThumbValue(thumbs.indexOf(thumb));
146        }
147        
148        private int getThumbIndex(ThumbComp thumb) {
149            return thumbs.indexOf(thumb);
150        }
151        
152        private void clipThumbPosition(ThumbComp thumb) {
153            if(getThumbValue(thumb) < getModel().getMinimumValue()) {
154            getModel().getThumbAt(getThumbIndex(thumb)).setPosition(
155            getModel().getMinimumValue());
156            }
157            if(getThumbValue(thumb) > getModel().getMaximumValue()) {
158            getModel().getThumbAt(getThumbIndex(thumb)).setPosition(
159            getModel().getMaximumValue());
160            }
161        }
162            
163        public ThumbRenderer getThumbRenderer() {
164            return thumbRenderer;
165        }
166    
167        public void setThumbRenderer(ThumbRenderer thumbRenderer) {
168            this.thumbRenderer = thumbRenderer;
169        }
170    
171        public TrackRenderer getTrackRenderer() {
172            return trackRenderer;
173        }
174    
175        public void setTrackRenderer(TrackRenderer trackRenderer) {
176            this.trackRenderer = trackRenderer;
177        }
178        
179        public float getMinimumValue() {
180            return getModel().getMinimumValue();
181        }
182        
183        public void setMinimumValue(float minimumValue) {
184            getModel().setMinimumValue(minimumValue);
185        }
186        
187        public float getMaximumValue() {
188            return getModel().getMaximumValue();
189        }
190        
191        public void setMaximumValue(float maximumValue) {
192            getModel().setMaximumValue(maximumValue);
193        }
194    
195        private void setThumbPositionByX(ThumbComp selected) {    
196            float range = getModel().getMaximumValue()-getModel().getMinimumValue();
197            int x = selected.getX();
198            // adjust to the center of the thumb
199            x += selected.getWidth()/2;
200            // adjust for the leading space on the slider
201            x -= selected.getWidth()/2;
202            
203            int w = getWidth();
204            // adjust for the leading and trailing space on the slider
205            w -= selected.getWidth();
206            float delta = ((float)x)/((float)w);
207            int thumb_index = getThumbIndex(selected);
208            float value = delta*range;
209        getModel().getThumbAt(thumb_index).setPosition(value);
210            //getModel().setPositionAt(thumb_index,value);
211            clipThumbPosition(selected);
212        }
213        
214        private void setThumbXByPosition(ThumbComp thumb, float pos) {
215            float tu = pos;
216            float lp = getWidth()-thumb.getWidth();
217            float lu = getModel().getMaximumValue()-getModel().getMinimumValue();
218            float tp = (tu*lp)/lu;
219            thumb.setLocation((int)tp-thumb.getWidth()/2 + thumb.getWidth()/2, thumb.getY());
220        }
221        
222        private void recalc() {
223            for(ThumbComp th : thumbs) {
224                setThumbXByPosition(th,getModel().getThumbAt(getThumbIndex(th)).getPosition());
225            //getPositionAt(getThumbIndex(th)));
226            }
227        }
228        
229        public void setBounds(int x, int y, int w, int h) {
230            super.setBounds(x,y,w,h);
231            recalc();
232        }
233    
234        public JComponent getSelectedThumb() {
235            return selected;
236        }
237        
238        public int getSelectedIndex() {
239            return getThumbIndex(selected);
240        }
241            
242        public MultiThumbModel<E> getModel() {
243            return model;
244        }
245    
246        public void setModel(MultiThumbModel<E> model) {
247        if(this.model != null) {
248            this.model.removeThumbDataListener(tdl);
249        }
250            this.model = model;
251        this.model.addThumbDataListener(tdl);   
252        }
253        
254        public void addMultiThumbListener(ThumbListener listener) {
255            listeners.add(listener);
256        }
257    
258        private class MultiThumbMouseListener extends MouseInputAdapter {
259            private JXMultiThumbSlider slider;
260            
261            public MultiThumbMouseListener(JXMultiThumbSlider slider) {
262                this.slider = slider;
263            }
264    
265            public void mousePressed(MouseEvent evt) {
266                ThumbComp handle = findHandle(evt);
267                if(handle != null) {
268                    selected = handle;
269                    selected.setSelected(true);
270                    int thumb_index = getThumbIndex(selected);
271                    for(ThumbListener tl : listeners) {
272                        tl.thumbSelected(thumb_index);
273                    }
274                    repaint();
275                } else {
276                    selected = null;
277                    for(ThumbListener tl : listeners) {
278                        tl.thumbSelected(-1);
279                    }
280                    repaint();
281                }
282            for(ThumbListener tl : listeners) {
283            tl.mousePressed(evt);
284            }
285            }
286    
287            public void mouseReleased(MouseEvent evt) {
288                if(selected != null) {
289                    selected.setSelected(false);
290                }
291            }
292            
293            public void mouseDragged(MouseEvent evt) {
294                if(selected != null) {
295                    int nx = (int)evt.getPoint().getX()- selected.getWidth()/2;
296                    if(nx < 0) {
297                        nx = 0;
298                    }
299                    if(nx > getWidth()-selected.getWidth()) {
300                        nx = getWidth()-selected.getWidth();
301                    }
302                    selected.setLocation(nx,(int)selected.getLocation().getY());
303                    setThumbPositionByX(selected);
304                    int thumb_index = getThumbIndex(selected);
305                    //System.out.println("still dragging: " + thumb_index);
306                    for(ThumbListener mtl : listeners) {
307                        mtl.thumbMoved(thumb_index,getModel().getThumbAt(thumb_index).getPosition());
308                //getPositionAt(thumb_index));
309                    }
310                    repaint();
311                }
312            }
313    
314            
315            private ThumbComp findHandle(MouseEvent evt) {
316                for(ThumbComp hand : thumbs) {
317                    Point p2 = new Point();
318                    p2.setLocation(evt.getPoint().getX() - hand.getX(),
319                        evt.getPoint().getY() - hand.getY());
320                    if(hand.contains(p2)) {
321                        return hand;
322                    }
323                }
324                return null;
325            }
326        }
327        
328        private class ThumbComp extends JComponent {
329            
330            private JXMultiThumbSlider slider;
331            
332            public ThumbComp(JXMultiThumbSlider slider) {
333                this.slider = slider;
334                Dimension dim = new Dimension(10,10);//slider.getHeight());
335                if(slider.getThumbRenderer() != null) {
336                    JComponent comp = getRenderer();
337                    dim = comp.getPreferredSize();
338                }
339                setSize(dim);
340                setMinimumSize(dim);
341                setPreferredSize(dim);
342                setMaximumSize(dim);
343                setBackground(Color.white);
344            }
345            
346            public void paintComponent(Graphics g) {
347                if(slider.getThumbRenderer() != null) {
348                    JComponent comp = getRenderer();
349                    comp.setSize(this.getSize());
350                    System.out.println("painting: " + comp);
351                    comp.paint(g);
352                } else {
353                    g.setColor(getBackground());
354                    g.fillRect(0,0,getWidth(),getHeight());
355                    if(isSelected()) {
356                        g.setColor(Color.black);
357                        g.drawRect(0,0,getWidth()-1,getHeight()-1);
358                    }
359                }
360            }
361    
362            private JComponent getRenderer() {
363                JComponent comp = slider.getThumbRenderer().
364                        getThumbRendererComponent(slider,slider.getThumbIndex(this),isSelected());
365                return comp;
366            }
367            
368            private boolean selected;
369    
370            public boolean isSelected() {
371                return selected;
372            }
373    
374            public void setSelected(boolean selected) {
375                this.selected = selected;
376            }
377        }
378    
379        private class ThumbHandler implements ThumbDataListener {
380    
381            public void positionChanged(ThumbDataEvent e) {
382                System.out.println("position changed");
383            }
384    
385            public void thumbAdded(ThumbDataEvent evt) {
386                ThumbComp thumb = new ThumbComp(JXMultiThumbSlider.this);
387                thumb.setLocation(0, 0);
388                add(thumb);
389                thumbs.add(evt.getIndex(), thumb);
390                clipThumbPosition(thumb);
391                setThumbXByPosition(thumb, evt.getThumb().getPosition());
392                repaint();
393            }
394    
395            public void thumbRemoved(ThumbDataEvent evt) {
396                ThumbComp thumb = thumbs.get(evt.getIndex());
397                remove(thumb);
398                thumbs.remove(thumb);
399                repaint();
400            }
401    
402            public void valueChanged(ThumbDataEvent e) {
403                System.out.println("value changed");
404            }
405        }
406    
407    
408    }