001 /*
002 * $Id: JXMultiThumbSlider.java 3310 2009-03-26 10:25:24Z kleopatra $
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
024 import org.jdesktop.swingx.multislider.DefaultMultiThumbModel;
025 import org.jdesktop.swingx.multislider.MultiThumbModel;
026 import org.jdesktop.swingx.multislider.ThumbDataEvent;
027 import org.jdesktop.swingx.multislider.ThumbDataListener;
028 import org.jdesktop.swingx.multislider.ThumbListener;
029 import org.jdesktop.swingx.multislider.ThumbRenderer;
030 import org.jdesktop.swingx.multislider.TrackRenderer;
031 import org.jdesktop.swingx.plaf.MultiThumbSliderAddon;
032 import org.jdesktop.swingx.plaf.LookAndFeelAddons;
033 import org.jdesktop.swingx.plaf.MultiThumbSliderUI;
034
035 import javax.swing.*;
036 import javax.swing.event.MouseInputAdapter;
037 import java.awt.*;
038 import java.awt.event.MouseEvent;
039 import java.util.ArrayList;
040 import java.util.List;
041
042 /**
043 * <p>A slider which can have multiple control points or <i>Thumbs</i></p>
044 * <p>The thumbs each represent a value between the minimum and maximum values
045 * of the slider. Thumbs can pass each other when being dragged. Thumbs have
046 * no default visual representation. To customize the look of the thumbs and the
047 * track behind the thumbs you must provide a ThumbRenderer and a TrackRenderer
048 * implementation. To listen for changes to the thumbs you must provide an
049 * implemention of ThumbDataListener.
050 *
051 * TODOs:
052 * move public inner classes (interfaces, etc) to subpackage
053 * add min/maxvalue convenience methods to jxmultithumbslider
054 * add plafs for windows, mac, and basic (if necessary)
055 * make way to properly control the height.
056 * hide the inner thumb component
057 *
058 * @author joshy
059 */
060 public class JXMultiThumbSlider<E> extends JComponent {
061 static {
062 LookAndFeelAddons.contribute(new MultiThumbSliderAddon());
063 }
064
065 public static final String uiClassID = "MultiThumbSliderUI";
066
067 /** Creates a new instance of JMultiThumbSlider */
068 public JXMultiThumbSlider() {
069 thumbs = new ArrayList<ThumbComp>();
070 setLayout(null);
071
072 tdl = new ThumbHandler();
073
074 setModel(new DefaultMultiThumbModel<E>());
075 MultiThumbMouseListener mia = new MultiThumbMouseListener();
076 addMouseListener(mia);
077 addMouseMotionListener(mia);
078
079 Dimension dim = new Dimension(60,16);
080 setPreferredSize(dim);
081 setSize(dim);
082 setMinimumSize(new Dimension(30,16));
083 updateUI();
084 }
085
086 public MultiThumbSliderUI getUI() {
087 return (MultiThumbSliderUI)ui;
088 }
089
090 public void setUI(MultiThumbSliderUI ui) {
091 super.setUI(ui);
092 }
093
094 @Override
095 public void updateUI() {
096 setUI((MultiThumbSliderUI)LookAndFeelAddons.getUI(this, MultiThumbSliderUI.class));
097 invalidate();
098 }
099
100 /**
101 * {@inheritDoc}
102 */
103 @Override
104 public String getUIClassID() {
105 return uiClassID;
106 }
107
108 private ThumbDataListener tdl;
109
110 private List<ThumbComp> thumbs;
111
112 private ThumbRenderer thumbRenderer;
113
114 private TrackRenderer trackRenderer;
115
116 private MultiThumbModel<E> model;
117
118 private List<ThumbListener> listeners = new ArrayList<ThumbListener>();
119
120 private ThumbComp selected;
121
122 @Override
123 protected void paintComponent(Graphics g) {
124 if(isVisible()) {
125 if(trackRenderer != null) {
126 JComponent comp = trackRenderer.getRendererComponent(this);
127 add(comp);
128 comp.paint(g);
129 remove(comp);
130 } else {
131 paintRange((Graphics2D)g);
132 }
133 }
134 }
135
136 private void paintRange(Graphics2D g) {
137 g.setColor(Color.blue);
138 g.fillRect(0,0,getWidth(),getHeight());
139 }
140
141 private float getThumbValue(int thumbIndex) {
142 return getModel().getThumbAt(thumbIndex).getPosition();
143 }
144
145 private float getThumbValue(ThumbComp thumb) {
146 return getThumbValue(thumbs.indexOf(thumb));
147 }
148
149 private int getThumbIndex(ThumbComp thumb) {
150 return thumbs.indexOf(thumb);
151 }
152
153 private void clipThumbPosition(ThumbComp thumb) {
154 if(getThumbValue(thumb) < getModel().getMinimumValue()) {
155 getModel().getThumbAt(getThumbIndex(thumb)).setPosition(
156 getModel().getMinimumValue());
157 }
158 if(getThumbValue(thumb) > getModel().getMaximumValue()) {
159 getModel().getThumbAt(getThumbIndex(thumb)).setPosition(
160 getModel().getMaximumValue());
161 }
162 }
163
164 public ThumbRenderer getThumbRenderer() {
165 return thumbRenderer;
166 }
167
168 public void setThumbRenderer(ThumbRenderer thumbRenderer) {
169 this.thumbRenderer = thumbRenderer;
170 }
171
172 public TrackRenderer getTrackRenderer() {
173 return trackRenderer;
174 }
175
176 public void setTrackRenderer(TrackRenderer trackRenderer) {
177 this.trackRenderer = trackRenderer;
178 }
179
180 public float getMinimumValue() {
181 return getModel().getMinimumValue();
182 }
183
184 public void setMinimumValue(float minimumValue) {
185 getModel().setMinimumValue(minimumValue);
186 }
187
188 public float getMaximumValue() {
189 return getModel().getMaximumValue();
190 }
191
192 public void setMaximumValue(float maximumValue) {
193 getModel().setMaximumValue(maximumValue);
194 }
195
196 private void setThumbPositionByX(ThumbComp selected) {
197 float range = getModel().getMaximumValue()-getModel().getMinimumValue();
198 int x = selected.getX();
199 // adjust to the center of the thumb
200 x += selected.getWidth()/2;
201 // adjust for the leading space on the slider
202 x -= selected.getWidth()/2;
203
204 int w = getWidth();
205 // adjust for the leading and trailing space on the slider
206 w -= selected.getWidth();
207 float delta = ((float)x)/((float)w);
208 int thumb_index = getThumbIndex(selected);
209 float value = delta*range;
210 getModel().getThumbAt(thumb_index).setPosition(value);
211 //getModel().setPositionAt(thumb_index,value);
212 clipThumbPosition(selected);
213 }
214
215 private void setThumbXByPosition(ThumbComp thumb, float pos) {
216 float lp = getWidth()-thumb.getWidth();
217 float lu = getModel().getMaximumValue()-getModel().getMinimumValue();
218 float tp = (pos*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 @Override
230 public void setBounds(int x, int y, int w, int h) {
231 super.setBounds(x,y,w,h);
232 recalc();
233 }
234
235 public JComponent getSelectedThumb() {
236 return selected;
237 }
238
239 public int getSelectedIndex() {
240 return getThumbIndex(selected);
241 }
242
243 public MultiThumbModel<E> getModel() {
244 return model;
245 }
246
247 public void setModel(MultiThumbModel<E> model) {
248 if(this.model != null) {
249 this.model.removeThumbDataListener(tdl);
250 }
251 this.model = model;
252 this.model.addThumbDataListener(tdl);
253 }
254
255 public void addMultiThumbListener(ThumbListener listener) {
256 listeners.add(listener);
257 }
258
259 private class MultiThumbMouseListener extends MouseInputAdapter {
260 @Override
261 public void mousePressed(MouseEvent evt) {
262 ThumbComp handle = findHandle(evt);
263 if(handle != null) {
264 selected = handle;
265 selected.setSelected(true);
266 int thumb_index = getThumbIndex(selected);
267 for(ThumbListener tl : listeners) {
268 tl.thumbSelected(thumb_index);
269 }
270 repaint();
271 } else {
272 selected = null;
273 for(ThumbListener tl : listeners) {
274 tl.thumbSelected(-1);
275 }
276 repaint();
277 }
278 for(ThumbListener tl : listeners) {
279 tl.mousePressed(evt);
280 }
281 }
282
283 @Override
284 public void mouseReleased(MouseEvent evt) {
285 if(selected != null) {
286 selected.setSelected(false);
287 }
288 }
289
290 @Override
291 public void mouseDragged(MouseEvent evt) {
292 if(selected != null) {
293 int nx = (int)evt.getPoint().getX()- selected.getWidth()/2;
294 if(nx < 0) {
295 nx = 0;
296 }
297 if(nx > getWidth()-selected.getWidth()) {
298 nx = getWidth()-selected.getWidth();
299 }
300 selected.setLocation(nx,(int)selected.getLocation().getY());
301 setThumbPositionByX(selected);
302 int thumb_index = getThumbIndex(selected);
303 //log.fine("still dragging: " + thumb_index);
304 for(ThumbListener mtl : listeners) {
305 mtl.thumbMoved(thumb_index,getModel().getThumbAt(thumb_index).getPosition());
306 //getPositionAt(thumb_index));
307 }
308 repaint();
309 }
310 }
311
312
313 private ThumbComp findHandle(MouseEvent evt) {
314 for(ThumbComp hand : thumbs) {
315 Point p2 = new Point();
316 p2.setLocation(evt.getPoint().getX() - hand.getX(),
317 evt.getPoint().getY() - hand.getY());
318 if(hand.contains(p2)) {
319 return hand;
320 }
321 }
322 return null;
323 }
324 }
325
326 private static class ThumbComp extends JComponent {
327
328 private JXMultiThumbSlider<?> slider;
329
330 public ThumbComp(JXMultiThumbSlider<?> slider) {
331 this.slider = slider;
332 Dimension dim = new Dimension(10,10);//slider.getHeight());
333 /*if(slider.getThumbRenderer() != null) {
334 JComponent comp = getRenderer();
335 dim = comp.getPreferredSize();
336 }*/
337 setSize(dim);
338 setMinimumSize(dim);
339 setPreferredSize(dim);
340 setMaximumSize(dim);
341 setBackground(Color.white);
342 }
343
344 @Override
345 public void paintComponent(Graphics g) {
346 if(slider.getThumbRenderer() != null) {
347 JComponent comp = getRenderer();
348 comp.setSize(this.getSize());
349 comp.paint(g);
350 } else {
351 g.setColor(getBackground());
352 g.fillRect(0,0,getWidth(),getHeight());
353 if(isSelected()) {
354 g.setColor(Color.black);
355 g.drawRect(0,0,getWidth()-1,getHeight()-1);
356 }
357 }
358 }
359
360 private JComponent getRenderer() {
361 return slider.getThumbRenderer().
362 getThumbRendererComponent(slider,slider.getThumbIndex(this),isSelected());
363 }
364
365 private boolean selected;
366
367 public boolean isSelected() {
368 return selected;
369 }
370
371 public void setSelected(boolean selected) {
372 this.selected = selected;
373 }
374 }
375
376 private class ThumbHandler implements ThumbDataListener {
377
378 public void positionChanged(ThumbDataEvent e) {
379 ThumbComp comp = thumbs.get(e.getIndex());
380 clipThumbPosition(comp);
381 setThumbXByPosition(comp, e.getThumb().getPosition());
382 repaint();
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 repaint();
404 }
405 }
406
407
408 }