/******************************************************************************* * Copyright (c) 2016 Weasis Team and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Nicolas Roduit - initial API and implementation *******************************************************************************/ package org.weasis.core.api.gui.util; import java.awt.event.InputEvent; import java.awt.event.MouseEvent; import java.awt.event.MouseWheelEvent; import java.util.Hashtable; import javax.swing.BoundedRangeModel; import javax.swing.BoxLayout; import javax.swing.DefaultBoundedRangeModel; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.border.TitledBorder; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import org.weasis.core.api.service.AuditLog; import org.weasis.core.api.util.FontTools; import org.weasis.core.api.util.StringUtil; public abstract class SliderChangeListener extends MouseActionAdapter implements ChangeListener, ActionState { public static final int DEFAULT_SMALLEST = 0; public static final int DEFAULT_LARGEST = 4095; private final DefaultBoundedRangeModel model; protected final BasicActionState basicState; protected volatile boolean triggerAction = true; protected volatile boolean valueIsAdjusting = true; protected volatile Double realMin; protected volatile Double realMax; public SliderChangeListener(ActionW action, int min, int max, int value) { this(action, min, max, value, true); } public SliderChangeListener(ActionW action, int min, int max, int value, boolean valueIsAdjusting, double mouseSensivity) { this(action, min, max, value, valueIsAdjusting); setMouseSensivity(mouseSensivity); } public SliderChangeListener(ActionW action, int min, int max, int value, boolean valueIsAdjusting) { super(); this.basicState = new BasicActionState(action); this.valueIsAdjusting = valueIsAdjusting; model = new DefaultBoundedRangeModel(value, 0, min, max); model.addChangeListener(this); } public SliderChangeListener(ActionW action, double min, double max, double value, boolean valueIsAdjusting, double mouseSensivity, int sliderRange) { this.basicState = new BasicActionState(action); this.valueIsAdjusting = valueIsAdjusting; setMouseSensivity(mouseSensivity); model = new DefaultBoundedRangeModel(0, 0, 0, sliderRange); setRealMinMaxValue(min, max, value, false); model.addChangeListener(this); } @Override public void enableAction(boolean enabled) { basicState.enableAction(enabled); } @Override public boolean isActionEnabled() { return basicState.isActionEnabled(); } public void setSliderMinMax(int min, int max) { setSliderMinMaxValue(min, max, model.getValue()); } public void setSliderMinMaxValue(int min, int max, int value) { setSliderMinMaxValue(min, max, value, true); } public void setSliderMinMaxValue(int min, int max, int value, boolean triggerChangedEvent) { realMin = null; realMax = null; minMaxValueAction(min, max, value, triggerChangedEvent); } public void setRealMinMaxValue(double min, double max, double value) { setRealMinMaxValue(min, max, value, true); } public void setRealMinMaxValue(double min, double max, double value, boolean triggerChangedEvent) { realMin = min; realMax = max; minMaxValueAction(toSliderValue(min), toSliderValue(max), toSliderValue(value), triggerChangedEvent); } private synchronized void minMaxValueAction(int min, int max, int value, boolean trigger) { if (min > max) { throw new IllegalStateException("min > max"); //$NON-NLS-1$ } // Adjust the value to min and max to avoid the model to change the min and the max int v = (value > max) ? max : ((value < min) ? min : value); boolean oldTrigger = triggerAction; triggerAction = trigger; model.setRangeProperties(v, model.getExtent(), min, max, model.getValueIsAdjusting()); triggerAction = oldTrigger; boolean paintThicks = max < 65536; for (Object c : basicState.getComponents()) { if (c instanceof JSliderW) { JSliderW s = (JSliderW) c; if (s.isShowLabels()) { // When range becomes big do not display thick (can be very slow) and labels s.setPaintTicks(paintThicks); s.setPaintLabels(paintThicks); } updateSliderProoperties(s); setSliderLabelValues(s, min, max, realMin, realMax); } } } public boolean isTriggerAction() { return triggerAction; } @Override public ActionW getActionW() { return basicState.getActionW(); } public void setSliderValue(int value) { model.setValue(value); } public void setSliderValue(int value, boolean triggerChangedEvent) { if (triggerChangedEvent) { setSliderValue(value); } else { boolean ajusting = valueIsAdjusting ? true : !model.getValueIsAdjusting(); if (ajusting) { boolean oldTrigger = triggerAction; triggerAction = false; setSliderValue(value); triggerAction = oldTrigger; } } } public void setRealValue(double value) { model.setValue(toSliderValue(value)); } public void setRealValue(double value, boolean triggerChangedEvent) { if (triggerChangedEvent) { setRealValue(value); } else { boolean ajusting = valueIsAdjusting ? true : !model.getValueIsAdjusting(); if (ajusting) { boolean oldTrigger = triggerAction; triggerAction = false; setRealValue(value); triggerAction = oldTrigger; } } } public boolean isValueIsAdjusting() { return valueIsAdjusting; } public int getSliderMin() { return model.getMinimum(); } public int getSliderMax() { return model.getMaximum(); } public int getSliderValue() { return model.getValue(); } public double getRealValue() { return toModelValue(model.getValue()); } public DefaultBoundedRangeModel getSliderModel() { return model; } public String getValueToDisplay() { return getDisplayedModelValue(getSliderValue(), getSliderMax(), realMin, realMax); } @Override public void stateChanged(ChangeEvent evt) { boolean ajusting = valueIsAdjusting ? true : !model.getValueIsAdjusting(); if (triggerAction && ajusting) { stateChanged(model); AuditLog.LOGGER.info("action:{} val:{} min:{} max:{}", //$NON-NLS-1$ new Object[] { basicState.getActionW().cmd(), model.getValue(), model.getMinimum(), model.getMaximum() }); } for (Object c : basicState.getComponents()) { if (c instanceof JSliderW) { updateSliderProoperties((JSliderW) c); } } } public abstract void stateChanged(BoundedRangeModel model); @Override public String toString() { return basicState.getActionW().getTitle(); } @Override public boolean registerActionState(Object c) { if (basicState.registerActionState(c)) { if (c instanceof JSliderW) { JSliderW slider = (JSliderW) c; slider.setModel(model); updateSliderProoperties(slider); } return true; } return false; } @Override public void unregisterActionState(Object c) { basicState.unregisterActionState(c); if (c instanceof JSliderW) { ((JSliderW) c).setModel(new DefaultBoundedRangeModel(0, 0, 0, 100)); } } public static void setSliderLabelValues(JSliderW slider, final int min, int max) { setSliderLabelValues(slider, min, max, null, null); } public static void setSliderLabelValues(JSliderW slider, final int min, int max, final Double realMin, final Double realMax) { final int div = slider.getLabelDivision(); if (div < 1) { return; } int space = (max - min) / (div - 1); final int spacing = space < 1 ? 1 : space; if (!slider.getPaintLabels()) { return; } final Hashtable<Integer, JLabel> table = new Hashtable<>(); GuiExecutor.instance().invokeAndWait(() -> { for (int i = 0; i < div; i++) { int index = i * spacing + min; table.put(index, new JLabel(getDisplayedModelValue(i * spacing + min, max, realMin, realMax))); } }); slider.setLabelTable(table); FontTools.setFont10(slider); slider.setMajorTickSpacing(spacing); } private static String getDisplayedModelValue(int sliderValue, int sliderMax, Double modelMin, Double modelMax) { if (modelMin == null || modelMax == null) { return Integer.toString(sliderValue); } double realVal = toModelValue(sliderValue, sliderMax, modelMin, modelMax); return DecFormater.twoDecimal(realVal); } public void updateSliderProoperties(JSliderW slider) { JPanel panel = (JPanel) slider.getParent(); String result = basicState.getActionW().getTitle() + StringUtil.COLON_AND_SPACE + getValueToDisplay(); if (slider.isdisplayValueInTitle() && panel != null && panel.getBorder() instanceof TitledBorder) { ((TitledBorder) panel.getBorder()).setTitle(result); panel.repaint(); } else { slider.setToolTipText(result); } } @Override public void mousePressed(MouseEvent e) { if (basicState.isActionEnabled() && !e.isConsumed()) { int buttonMask = getButtonMaskEx(); if ((e.getModifiersEx() & buttonMask) != 0) { lastPosition = isMoveOnX() ? e.getX() : e.getY(); dragAccumulator = getSliderValue(); } } else { // Ensure to not enter in drag event when the mouse event is consumed dragAccumulator = Double.MAX_VALUE; } } @Override public void mouseDragged(MouseEvent e) { if (basicState.isActionEnabled() && !e.isConsumed()) { int buttonMask = getButtonMaskEx(); int modifier = e.getModifiersEx(); /* * dragAccumulator == Double.NaN when the listener did not catch the Pressed MouseEvent (could append in * multisplit container) */ if ((modifier & buttonMask) != 0 && MathUtil.isDifferent(dragAccumulator, Double.MAX_VALUE)) { int position = isMoveOnX() ? e.getX() : e.getY(); int mask = InputEvent.SHIFT_DOWN_MASK | InputEvent.CTRL_DOWN_MASK; // Accelerate the action if ctrl or shift is down double acceleratorKey = (modifier & mask) == 0 ? 1.0 : (modifier & mask) == mask ? 5.0 : 2.5; double val = (position - lastPosition) * getMouseSensivity() * acceleratorKey; if (MathUtil.isEqualToZero(val)) { return; } lastPosition = position; if (isInverse()) { dragAccumulator -= val; } else { dragAccumulator += val; } if (dragAccumulator < getSliderMin()) { dragAccumulator = getSliderMin(); } if (dragAccumulator > getSliderMax()) { dragAccumulator = getSliderMax(); } if (val < 0.0) { setSliderValue((int) Math.ceil(dragAccumulator)); } else { setSliderValue((int) Math.floor(dragAccumulator)); } } } } @Override public void mouseWheelMoved(MouseWheelEvent e) { if (basicState.isActionEnabled() && !e.isConsumed()) { setSliderValue(getSliderValue() + e.getWheelRotation() * e.getScrollAmount()); } } public JSliderW createSlider(int labelDivision, boolean displayValueInTitle) { final JPanel palenSlider1 = new JPanel(); palenSlider1.setLayout(new BoxLayout(palenSlider1, BoxLayout.Y_AXIS)); palenSlider1.setBorder(new TitledBorder(basicState.getActionW().getTitle())); JSliderW slider = new JSliderW(model.getMinimum(), model.getMaximum(), model.getValue()); slider.setLabelDivision(labelDivision); slider.setdisplayValueInTitle(displayValueInTitle); slider.setPaintTicks(true); slider.setShowLabels(labelDivision > 0); palenSlider1.add(slider); registerActionState(slider); if (slider.isShowLabels()) { slider.setPaintLabels(true); setSliderLabelValues(slider, model.getMinimum(), model.getMaximum(), realMin, realMax); } return slider; } public int toSliderValue(double modelValue) { Double modelMin = realMin; Double modelMax = realMax; if (modelMin == null || modelMax == null) { return (int) modelValue; } return (int) Math.round((modelValue - modelMin) * (model.getMaximum() / (modelMax - modelMin))); } public double toModelValue(int sliderValue) { return toModelValue(sliderValue, model.getMaximum(), realMin, realMax); } protected static double toModelValue(int sliderValue, int sliderMax, Double modelMin, Double modelMax) { if (modelMin == null || modelMax == null) { return sliderValue; } return (sliderValue * (modelMax - modelMin)) / sliderMax + modelMin; } }