View Javadoc

1   /*
2    * Copyright (c) 2006 Stiftung Deutsches Elektronen-Synchroton,
3    * Member of the Helmholtz Association, (DESY), HAMBURG, GERMANY.
4    *
5    * THIS SOFTWARE IS PROVIDED UNDER THIS LICENSE ON AN "../AS IS" BASIS.
6    * WITHOUT WARRANTY OF ANY KIND, EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED
7    * TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR PARTICULAR PURPOSE AND
8    * NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
9    * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
10   * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
11   * THE USE OR OTHER DEALINGS IN THE SOFTWARE. SHOULD THE SOFTWARE PROVE DEFECTIVE
12   * IN ANY RESPECT, THE USER ASSUMES THE COST OF ANY NECESSARY SERVICING, REPAIR OR
13   * CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE.
14   * NO USE OF ANY SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS DISCLAIMER.
15   * DESY HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS,
16   * OR MODIFICATIONS.
17   * THE FULL LICENSE SPECIFYING FOR THE SOFTWARE THE REDISTRIBUTION, MODIFICATION,
18   * USAGE AND OTHER RIGHTS AND OBLIGATIONS IS INCLUDED WITH THE DISTRIBUTION OF THIS
19   * PROJECT IN THE FILE LICENSE.HTML. IF THE LICENSE IS NOT INCLUDED YOU MAY FIND A COPY
20   * AT HTTP://WWW.DESY.DE/LEGAL/LICENSE.HTM
21   */
22  
23  package de.desy.acop.displayers.chart;
24  
25  import java.awt.Color;
26  import java.awt.Component;
27  import java.awt.Dimension;
28  import java.awt.Graphics;
29  import java.awt.GridBagConstraints;
30  import java.awt.GridBagLayout;
31  import java.awt.Insets;
32  import java.awt.event.ActionEvent;
33  import java.awt.event.ActionListener;
34  import java.awt.event.ItemEvent;
35  import java.awt.event.ItemListener;
36  import java.beans.PropertyChangeEvent;
37  import java.beans.PropertyChangeListener;
38  
39  import javax.swing.DefaultListCellRenderer;
40  import javax.swing.Icon;
41  import javax.swing.JButton;
42  import javax.swing.JCheckBox;
43  import javax.swing.JComboBox;
44  import javax.swing.JLabel;
45  import javax.swing.JPanel;
46  import javax.swing.ListCellRenderer;
47  import javax.swing.event.EventListenerList;
48  
49  import de.desy.acop.chart.AcopGraphStyleEnum;
50  
51  /**
52   * <code>ScalePanel</code> is a visual bean which allows selection and configuration
53   * of certain chart scaling properties. This bean is independent of any chart
54   * and only supplies the certain components for setting properties. Upon the change
55   * of any of the properties a <code>PropertyChangeEvent</code> is fired which notifies
56   * all registered listeners.
57   *  
58   * @author <a href="mailto:jaka.bobnar@cosylab.com">Jaka Bobnar</a>
59   * @version $Id: Templates.xml,v 1.10 2004/01/13 16:17:13 jbobnar Exp $
60   *
61   */
62  public class ScalePanel extends JPanel {
63  	
64  	private static final long serialVersionUID = 1L;
65  	private JButton defaultScaleButton;
66  	private JCheckBox autoScaleCheckBox;
67  	private JComboBox linLogCombo;
68  	private JLabel logCriticalLabel;
69  	private JButton paneUpButton;
70  	private JButton paneDownButton;
71  	private JButton zoomInButton;
72  	private JButton zoomOutButton;
73  	private MinMaxPanel minMaxPanel;
74  
75  	public static final String CLEAR_CMD = "clear";
76  	public static final String DEFAULT_SCALE_CMD = "defaultScale";
77  	public static final String PANE_DOWN_ACTION = "down";
78  	public static final String PANE_UP_ACTION = "up";
79  	public static final String ZOOM_IN_ACTION = "zoomIn";
80  	public static final String ZOOM_OUT_ACTION = "zoomOut";
81  	private static final String LOG_CRITICAL = "Log not avail.";
82  	
83  	private AcopGraphStyleEnum selectedStyle = AcopGraphStyleEnum.LinLin;
84  	private AcopGraphStyleEnum[] availableStyles = AcopGraphStyleEnum.values();
85  	private boolean logCritical = false;
86  	private boolean autoScale = false;
87  	private boolean relativeScaleMode = false;
88  	private double defaultMin = 0;
89  	private double defaultMax = 0;
90  	private boolean minMaxEnabled = true;
91  	
92  	private EventListenerList listenerList = new EventListenerList();
93  	
94  	private double forcedDx = 1;
95  	
96  	/**
97  	 * Constructs a new ScalePanel.
98  	 *
99  	 */
100 	public ScalePanel() {
101 		super();
102 		initialize();
103 	}
104 	
105 	private void initialize() {
106 		setLayout(new GridBagLayout());
107 		
108 		JPanel shiftZoomPanel = new JPanel(new GridBagLayout());
109 		
110 		paneDownButton = new JButton(getUpDownIcon(0));
111 		paneDownButton.setToolTipText("Pane down");
112 		paneDownButton.setActionCommand(PANE_DOWN_ACTION);
113 		paneDownButton.addActionListener(new ActionListener(){
114 			public void actionPerformed(ActionEvent e) {
115 				if (isAutoScale()) setAutoScale(false);
116 				double dx = forcedDx*(getMinMaxPanel().getMax() - getMinMaxPanel().getMin())/10;
117 				getMinMaxPanel().setMax(getMax() + dx);
118 				getMinMaxPanel().setMin(getMin() + dx);
119 				getMinMaxPanel().setRescaleEnabled(false);
120 				fireActionEvent(e);
121 			}
122 		});
123 		shiftZoomPanel.add(paneDownButton, new GridBagConstraints(3, 0, 1, 1, 1, 1, 14, 0, new Insets(2, 2, 2, 2), 0, 0));
124 		
125 		
126 		paneUpButton = new JButton(getUpDownIcon(1));
127 		paneUpButton.setToolTipText("Pane up");
128 		paneUpButton.setActionCommand(PANE_UP_ACTION);
129 		paneUpButton.addActionListener(new ActionListener(){
130 			public void actionPerformed(ActionEvent e) {
131 				if (isAutoScale()) setAutoScale(false);
132 				double dx = forcedDx*(getMinMaxPanel().getMax() - getMinMaxPanel().getMin())/10;
133 				getMinMaxPanel().setMax(getMax() - dx);
134 				getMinMaxPanel().setMin(getMin() - dx);
135 				getMinMaxPanel().setRescaleEnabled(false);
136 				fireActionEvent(e);
137 			}
138 		});
139 		shiftZoomPanel.add(paneUpButton, new GridBagConstraints(2, 0, 1, 1, 1, 1, 14, 0, new Insets(2, 2, 2, 2), 0, 0));
140 		
141 		zoomInButton = new JButton(getUpDownIcon(2));
142 		zoomInButton.setToolTipText("Zoom in");
143 		zoomInButton.setActionCommand(ZOOM_IN_ACTION);
144 		zoomInButton.addActionListener(new ActionListener(){
145 			public void actionPerformed(ActionEvent e) {
146 				if (isAutoScale()) setAutoScale(false);
147 				double dx = forcedDx*(getMinMaxPanel().getMax() - getMinMaxPanel().getMin())/22;
148 				double max = getMax() - dx;
149 				double min = getMin() + dx;
150 				//java has some rounding problems from time to time. in such cases just reset the value to 0
151 				if (Math.abs(max) < 1E-16) {
152 					max = 0;
153 				}
154 				if (Math.abs(min) < 1E-16) {
155 					min = 0;
156 				}
157 				getMinMaxPanel().setMax(max);
158 				getMinMaxPanel().setMin(min);
159 				getMinMaxPanel().setRescaleEnabled(false);
160 				fireActionEvent(e);
161 			}
162 		});
163 		shiftZoomPanel.add(zoomInButton, new GridBagConstraints(0, 0, 1, 1, 1, 1, 14, 0, new Insets(2, 2, 2, 2), 0, 0));
164 		
165 		zoomOutButton = new JButton(getUpDownIcon(3));
166 		zoomOutButton.setToolTipText("Zoom out");
167 		zoomOutButton.setActionCommand(ZOOM_OUT_ACTION);
168 		zoomOutButton.addActionListener(new ActionListener(){
169 			public void actionPerformed(ActionEvent e) {
170 				if (isAutoScale()) setAutoScale(false);
171 				double dx = forcedDx*(getMinMaxPanel().getMax() - getMinMaxPanel().getMin())/20;;
172 				double max = getMax() + dx;
173 				double min = getMin() - dx;
174 				//java has some rounding problems from time to time. in such cases just reset the value to 0
175 				if (Math.abs(max) < 1E-16) {
176 					max = 0;
177 				}
178 				if (Math.abs(min) < 1E-16) {
179 					min = 0;
180 				}
181 				getMinMaxPanel().setMax(max);
182 				getMinMaxPanel().setMin(min);
183 				getMinMaxPanel().setRescaleEnabled(false);
184 				fireActionEvent(e);
185 			}
186 		});
187 		shiftZoomPanel.add(zoomOutButton, new GridBagConstraints(1, 0, 1, 1, 1, 1, 14, 0, new Insets(2, 2, 2, 2), 0, 0));
188 		
189 		this.add(shiftZoomPanel, new GridBagConstraints(0,0,3,1,0,0,GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(2,4,2,2),0,0));
190 			
191 		this.add(getMinMaxPanel(), new GridBagConstraints(0,1,1,2,1,0,GridBagConstraints.NORTHWEST, GridBagConstraints.BOTH, new Insets(2,4,2,2),0,0));
192 		
193 		JPanel rightPanel = new JPanel(new GridBagLayout());
194 		
195 		linLogCombo = new JComboBox(availableStyles);		
196 		linLogCombo.addItemListener(new ItemListener(){
197 			public void itemStateChanged(ItemEvent e) {
198 				if (e.getStateChange() == ItemEvent.SELECTED) {
199 					setStyle((AcopGraphStyleEnum)linLogCombo.getSelectedItem());
200 				} 
201 			}
202 		});
203 		linLogCombo.setMinimumSize(new Dimension(80,21));
204 		linLogCombo.setPreferredSize(new Dimension(80,21));
205 		linLogCombo.setToolTipText("Scale Type of the Trace");
206 		
207 		rightPanel.add(linLogCombo, new GridBagConstraints(0,0,1,1,1,0,GridBagConstraints.EAST, GridBagConstraints.NONE, new Insets(2,2,2,4),0,0));
208 		
209 		logCriticalLabel = new JLabel(" ");
210 		logCriticalLabel.setOpaque(true);
211 		rightPanel.add(logCriticalLabel, new GridBagConstraints(0,1,1,1,1,0,GridBagConstraints.SOUTHEAST, GridBagConstraints.NONE, new Insets(2,2,2,4),0,0));
212 //		logCriticalLabel.setVisible(false);
213 
214 		this.add(rightPanel, new GridBagConstraints(1,1,1,2,1,0,GridBagConstraints.NORTHEAST, GridBagConstraints.NONE, new Insets(2,4,2,2),0,0));
215 		
216 		defaultScaleButton = new JButton("Default Scale");
217 		defaultScaleButton.setActionCommand(ScalePanel.DEFAULT_SCALE_CMD);
218 		defaultScaleButton.addActionListener(new ActionListener(){
219 			public void actionPerformed(ActionEvent e) {
220 				setAutoScale(false);
221 				if (getMax() != getDefaultMax()) {
222 					setMax(getDefaultMax());
223 				} else {
224 					firePropertyChange("max", null, getMax());
225 				}
226 				if (getMin() != getDefaultMin()) {
227 					setMin(getDefaultMin());
228 				} else {
229 					firePropertyChange("min", null, getMin());
230 				}
231 			}
232 		});
233 		defaultScaleButton.setToolTipText("Apply Default Scale");
234 		
235 		this.add(defaultScaleButton, new GridBagConstraints(0,3,1,1,1,0,GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(2,4,4,2),0,0));
236 
237 		autoScaleCheckBox = new JCheckBox("Auto Scale");
238 		autoScaleCheckBox.setHorizontalTextPosition(JCheckBox.LEADING);
239 		autoScaleCheckBox.addItemListener(new ItemListener(){
240 			public void itemStateChanged(ItemEvent e) {
241 				setAutoScale(autoScaleCheckBox.isSelected());
242 			}
243 		});
244 		autoScaleCheckBox.setEnabled(false);
245 		autoScaleCheckBox.setToolTipText("Toggle Optimal Autoscale");
246 		this.add(autoScaleCheckBox, new GridBagConstraints(1,3,1,1,1,0,GridBagConstraints.EAST, GridBagConstraints.NONE, new Insets(2,2,2,0),0,0));
247 		
248 		
249 	}
250 	
251 	private MinMaxPanel getMinMaxPanel() {
252 		if (minMaxPanel == null) {
253 			minMaxPanel = new MinMaxPanel();
254 			minMaxPanel.addPropertyChangeListener(new PropertyChangeListener(){
255 				public void propertyChange(PropertyChangeEvent evt) {
256 					String name = evt.getPropertyName();
257 					if ("max".equals(name) || "min".equals(name) && minMaxEnabled) {
258 						firePropertyChange(name, evt.getOldValue(), evt.getNewValue());
259 					}
260 				}
261 			});
262 		}
263 		return minMaxPanel;
264 	}
265 	
266 	/**
267 	 * Sets the flag whether extra digits are shown on this panel or not. If true
268 	 * the precision of all numbers is larger than if false.
269 	 * 
270 	 * @param extraDigits true if high precision should be used
271 	 */
272 	public void setExtraDigits(boolean extraDigits) {
273 		if (isExtraDigits() == extraDigits) return;
274 		getMinMaxPanel().setExtraDigits(extraDigits);
275 		firePropertyChange("extraDigits", !extraDigits, extraDigits);
276 	}
277 	
278 	/**
279 	 * Returns true if this panel shows extra digits.
280 	 * 
281 	 * @return true if extra digits are shown
282 	 */
283 	public boolean isExtraDigits() {
284 		return getMinMaxPanel().isExtraDigits();
285 	}
286 	
287 	/**
288 	 * Adds the action listener which is notified when pane up or down is pressed.
289 	 * 
290 	 * @param l the listener to add
291 	 */
292 	public void addActionListener(ActionListener l) {
293 		listenerList.add(ActionListener.class,l);
294 	}
295 	
296 	/**
297 	 * Removes the action listener.
298 	 * 
299 	 * @param l the listener to remove
300 	 */
301 	public void removeActionListener(ActionListener l) {
302 		listenerList.remove(ActionListener.class,l);
303 	}
304 	
305 	/**
306 	 * Notifies all registered action listeners about the event that ocurred.
307 	 * 
308 	 * @param e the event
309 	 */
310 	private void fireActionEvent(ActionEvent e) {
311 		Object[] listeners = listenerList.getListenerList();
312 		for (int i = 0; i < listeners.length; i+=2) {
313 			if (listeners[i] == ActionListener.class) {
314 				((ActionListener)listeners[i+1]).actionPerformed(e);
315 			}
316 		}
317 	}
318 	
319 	/*
320 	 * (non-Javadoc)
321 	 * @see javax.swing.JComponent#setEnabled(boolean)
322 	 */
323 	@Override
324 	public void setEnabled(boolean e) {
325 		super.setEnabled(e);
326 		getMinMaxPanel().setEnabled(e & relativeScaleMode & minMaxEnabled);
327 		autoScaleCheckBox.setEnabled(e & relativeScaleMode);
328 		defaultScaleButton.setEnabled(e & relativeScaleMode);
329 		linLogCombo.setEnabled(e & !logCritical);
330 		paneDownButton.setEnabled(e & relativeScaleMode);
331 		paneUpButton.setEnabled(e & relativeScaleMode);
332 		zoomInButton.setEnabled(e & relativeScaleMode);
333 		zoomOutButton.setEnabled(e & relativeScaleMode);
334 	}	
335 	
336 	/**
337 	 * Returns the auto scale property value.
338 	 * @return the auto scale
339 	 */
340 	public boolean isAutoScale() {
341 		return autoScaleCheckBox.isSelected();
342 	}
343 	
344 	/**
345 	 * Sets the auto scale property.
346 	 * 
347 	 * @param auto new auto scale value
348 	 */
349 	public void setAutoScale(boolean auto) {
350 		if (this.autoScale == auto) return;
351 		boolean oldAutoScale = this.autoScale;
352 		this.autoScale = auto;
353 		autoScaleCheckBox.setSelected(this.autoScale);
354 		firePropertyChange("autoScale", oldAutoScale, this.autoScale);
355 	}
356 	
357 	/**
358 	 * Sets the relative scale mode. The majority of properties is enabled
359 	 * only when in the relative scale mode.
360 	 * 
361 	 * @param relative 
362 	 */
363 	public void setRelativeScaleMode(boolean relative) {
364 		if (relativeScaleMode == relative) return;
365 		this.relativeScaleMode = relative;
366 		boolean enabled = isEnabled();
367 		autoScaleCheckBox.setEnabled(enabled & relative);
368 		minMaxPanel.setEnabled(enabled & relative & minMaxEnabled);
369 		zoomInButton.setEnabled(enabled & relative);
370 		zoomOutButton.setEnabled(enabled & relative);
371 		paneDownButton.setEnabled(enabled & relative);
372 		paneUpButton.setEnabled(enabled & relative);
373 		defaultScaleButton.setEnabled(enabled & relative);
374 		linLogCombo.setEnabled(isEnabled() & !logCritical);
375 		firePropertyChange("relativeScaleMode", !relative, relative);
376 	}
377 	
378 	/**
379 	 * Enables/disables the input panel for minimum and maximum.
380 	 * 
381 	 * @param enabled
382 	 */
383 	public void setMinMaxEnabled(boolean enabled) {
384 		if (this.minMaxEnabled == enabled) return;
385 		this.minMaxEnabled = enabled;
386 		minMaxPanel.setEnabled(isEnabled() & relativeScaleMode & minMaxEnabled);
387 		firePropertyChange("minMaxEnabled", !enabled, enabled);
388 	}
389 	
390 	/**
391 	 * Returns the relative scale mode.
392 	 * 
393 	 * @return relative scale mode
394 	 */
395 	public boolean getRelativeScaleMode() {
396 		return relativeScaleMode;
397 	}
398 
399 	/**
400 	 * Returns true if log critical warning is shown.
401 	 * @return true if critical for logarithmic values
402 	 */
403 	public boolean isLogCritical() {
404 		return logCritical;
405 	}
406 
407 	/**
408 	 * Shows/hides label showing a warning that data is logarithmically critical.
409 	 * @param logCritical
410 	 */
411 	public void setLogCritical(boolean logCritical) {
412 		if (this.logCritical == logCritical) return;
413 		this.logCritical = logCritical;
414 		if (logCritical) {
415 			logCriticalLabel.setText(LOG_CRITICAL);
416 		} else {
417 			logCriticalLabel.setText(" ");
418 		}
419 		linLogCombo.setEnabled(isEnabled() & !logCritical);
420 		firePropertyChange("logCritical", !logCritical, logCritical);
421 	}
422 
423 	/**
424 	 * Returns the maximum value.
425 	 * @return the max
426 	 */
427 	public double getMax() {
428 		return getMinMaxPanel().getMax();
429 	}
430 
431 	/**
432 	 * Sets maximum value.
433 	 * @param max new maximum value
434 	 */
435 	public void setMax(double max) {
436 		getMinMaxPanel().setMax(max);
437 	}
438 	
439 	/**
440 	 * Sets the maximum value and notifies the listeners of property change if
441 	 * requested.
442 	 * 
443 	 * @param max new maximum value
444 	 * @param notify should listeners be notified
445 	 */
446 	public void setMax(double max, boolean notify) {
447 		getMinMaxPanel().setMax(max, notify);
448 	}
449 
450 	/**
451 	 * Returns the minimum value.
452 	 * @return the minimum value
453 	 */
454 	public double getMin() {
455 		return getMinMaxPanel().getMin();
456 	}
457 
458 	/**
459 	 * Sets the minimum.
460 	 * @param min ne wminimum value
461 	 */
462 	public void setMin(double min) {
463 		getMinMaxPanel().setMin(min);
464 	}
465 	
466 	/**
467 	 * Sets the minimum value and notifies the listeners of property change if
468 	 * requested.
469 	 * 
470 	 * @param min new minimum value
471 	 * @param notify should listeners be notified
472 	 */
473 	public void setMin(double min, boolean notify) {
474 		getMinMaxPanel().setMin(min, notify);
475 	}
476 
477 	/**
478 	 * Returns the default maximum value.
479 	 * @return the defaultMax
480 	 */
481 	public double getDefaultMax() {
482 		return defaultMax;
483 	}
484 
485 	/**
486 	 * Sets the default maximum value.
487 	 * @param defaultMax new default maximum
488 	 */
489 	public void setDefaultMax(double defaultMax) {
490 		if (this.defaultMax == defaultMax) return;
491 		double oldValue = getDefaultMax();
492 		this.defaultMax = defaultMax;
493 		firePropertyChange("defaultMax", oldValue, getDefaultMax());
494 	}
495 
496 	/**
497 	 * Returns the default minimum value.
498 	 * @return the defaultMin
499 	 */
500 	public double getDefaultMin() {
501 		return defaultMin;
502 	}
503 
504 	/**
505 	 * Sets the default minimum value.
506 	 * @param defaultMin new default minimum
507 	 */
508 	public void setDefaultMin(double defaultMin) {
509 		if (this.defaultMin == defaultMin) return;
510 		double oldValue = getDefaultMin();
511 		this.defaultMin = defaultMin;
512 		firePropertyChange("defaultMin", oldValue, getDefaultMin());
513 	}
514 
515 	/**
516 	 * Return the style selected in the combo.
517 	 * @return return the selected axis scale 
518 	 */
519 	public AcopGraphStyleEnum getStyle() {
520 		return selectedStyle;
521 	}
522 
523 	/**
524 	 * Sets selected style. The selected style needs to be one of the avaialble styles.
525 	 * @param style new style
526 	 */
527 	public void setStyle(AcopGraphStyleEnum style) {
528 		if (this.selectedStyle == style) return;
529 		boolean ok = false;
530 		for (AcopGraphStyleEnum s : availableStyles) {
531 			if (s.equals(style)) {
532 				ok = true;
533 			}
534 		}
535 		if (!ok) return;
536 		AcopGraphStyleEnum oldValue = this.selectedStyle;
537 		this.selectedStyle = style;
538 		linLogCombo.setSelectedItem(this.selectedStyle);
539 		firePropertyChange("style", oldValue, this.selectedStyle);
540 	}
541 	
542 	/**
543 	 * Sets the styles which are available for selection.
544 	 * 
545 	 * @param styles
546 	 */
547 	public void setAvailableStyles(AcopGraphStyleEnum[] styles) {
548 		AcopGraphStyleEnum[] oldValue = this.availableStyles;
549 		availableStyles = styles;
550 		linLogCombo.removeAllItems();
551 		for (AcopGraphStyleEnum s : availableStyles) {
552 			linLogCombo.addItem(s);
553 		}
554 		firePropertyChange("availableStyles", oldValue, availableStyles);
555 	}
556 	
557 	/**
558 	 * Returns the styles which are available for selection.
559 	 * 
560 	 * @return
561 	 */
562 	public AcopGraphStyleEnum[] getAvailableStyles() {
563 		return availableStyles;
564 	}
565 	
566 	/**
567 	 * Sets the style combo renderer. The renderer should expect
568 	 * the values to be of type {@link AcopGraphStyleEnum}.
569 	 * 
570 	 * @param renderer the renderer to used in combo
571 	 */
572 	public void setStyleComboRenderer(ListCellRenderer renderer) {
573 		if (renderer != null) {
574 			linLogCombo.setRenderer(renderer);
575 		} else {
576 			linLogCombo.setRenderer(new DefaultListCellRenderer());
577 		}
578 	}
579 	
580 	/**
581 	 * Generates icon with up or down arrow. 
582 	 * 
583 	 * @param i if i == 0, down arrow is returned, if i == 1, up arrow is returned, if i == 2, "+" icon is returned, else "-" icon is returned
584 	 * @return arrow icon
585 	 */
586 	private Icon getUpDownIcon(int i){
587 		switch(i){
588 		case 0:
589 			// down arrow
590 			return new Icon(){
591 				public int getIconHeight() {
592 					return 10;
593 				}
594 				public int getIconWidth() {
595 					return 10;
596 				}
597 				public void paintIcon(Component c, Graphics g, int x, int y) {
598 					if (!c.isEnabled())g.setColor(Color.LIGHT_GRAY);
599 					else g.setColor(c.getForeground());
600 					g.drawLine(x, y + 4, x + 4, y + 8);
601 					g.drawLine(x + 4, y + 8, x + 8, y + 4);
602 					g.drawLine(x + 4, y, x + 4, y + 8);
603 				}
604 			};
605 		
606 		case 1:
607 			// up arrow
608 			return new Icon(){
609 				public int getIconHeight() {
610 					return 10;
611 				}
612 				public int getIconWidth() {
613 					return 10;
614 				}
615 				public void paintIcon(Component c, Graphics g, int x, int y) {
616 					if (!c.isEnabled())g.setColor(Color.LIGHT_GRAY);
617 					else g.setColor(c.getForeground());
618 					g.drawLine(x, y + 4, x + 4, y);
619 					g.drawLine(x + 4, y, x + 8, y + 4);
620 					g.drawLine(x + 4, y, x + 4, y + 8);
621 				}
622 			};
623 		
624 		case 2:
625 			// plus icon
626 			return new Icon(){
627 				public int getIconHeight() {
628 					return 10;
629 				}
630 				public int getIconWidth() {
631 					return 10;
632 				}
633 				public void paintIcon(Component c, Graphics g, int x, int y) {
634 					if (!c.isEnabled())g.setColor(Color.LIGHT_GRAY);
635 					else g.setColor(c.getForeground());
636 					g.drawLine(x, y + 4, x + 8, y + 4);
637 					g.drawLine(x + 4, y, x + 4, y + 8);
638 				}
639 			};
640 			
641 		default:
642 			// minus icon
643 			return new Icon(){
644 				public int getIconHeight() {
645 					return 10;
646 				}
647 				public int getIconWidth() {
648 					return 10;
649 				}
650 				public void paintIcon(Component c, Graphics g, int x, int y) {
651 					if (!c.isEnabled())g.setColor(Color.LIGHT_GRAY);
652 					else g.setColor(c.getForeground());
653 					g.drawLine(x, y + 4, x + 8, y + 4);
654 				}
655 			};
656 		}
657 	}
658 	
659 	/**
660 	 * Sets the forced dx. This dx will be used to multiply the original step
661 	 * when performing zoom in/out and scale up/down.
662 	 * 
663 	 * @param forced new dx fraction
664 	 */
665 	public void setForcedDxFraction(double forced) {
666 		if (this.forcedDx == forced) return;
667 		double oldValue = this.forcedDx;
668 		this.forcedDx = forced;
669 		firePropertyChange("setForcedDxFraction",oldValue,forcedDx);
670 	}
671 	
672 	/**
673 	 * Returns the forced dx fraction.
674 	 * 
675 	 * @return forced dx fraction
676 	 */
677 	public double getForcedDxFraction() {
678 		return forcedDx;
679 	}
680 }