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 }