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 }