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