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 }