source: josm/trunk/src/org/openstreetmap/josm/gui/dialogs/layer/LayerVisibilityAction.java@ 11510

Last change on this file since 11510 was 11510, checked in by Don-vip, 7 years ago

sonar - fb-contrib:ACEM_ABSTRACT_CLASS_EMPTY_METHODS

  • Property svn:eol-style set to native
File size: 15.3 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.dialogs.layer;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.Component;
7import java.awt.GridBagLayout;
8import java.awt.event.ActionEvent;
9import java.awt.event.MouseWheelEvent;
10import java.util.ArrayList;
11import java.util.Collection;
12import java.util.List;
13
14import javax.swing.AbstractAction;
15import javax.swing.BorderFactory;
16import javax.swing.ImageIcon;
17import javax.swing.JCheckBox;
18import javax.swing.JLabel;
19import javax.swing.JMenuItem;
20import javax.swing.JPanel;
21import javax.swing.JPopupMenu;
22import javax.swing.JSlider;
23
24import org.openstreetmap.josm.Main;
25import org.openstreetmap.josm.gui.SideButton;
26import org.openstreetmap.josm.gui.dialogs.IEnabledStateUpdating;
27import org.openstreetmap.josm.gui.dialogs.LayerListDialog.LayerListModel;
28import org.openstreetmap.josm.gui.layer.ImageryLayer;
29import org.openstreetmap.josm.gui.layer.Layer;
30import org.openstreetmap.josm.gui.layer.Layer.LayerAction;
31import org.openstreetmap.josm.gui.layer.imagery.ImageryFilterSettings;
32import org.openstreetmap.josm.tools.GBC;
33import org.openstreetmap.josm.tools.ImageProvider;
34import org.openstreetmap.josm.tools.Utils;
35
36/**
37 * This is a menu that includes all settings for the layer visibility. It combines gamma/opacity sliders and the visible-checkbox.
38 *
39 * @author Michael Zangl
40 */
41public final class LayerVisibilityAction extends AbstractAction implements IEnabledStateUpdating, LayerAction {
42 private static final int SLIDER_STEPS = 100;
43 /**
44 * Steps the value is changed by a mouse wheel change (one full click)
45 */
46 private static final int SLIDER_WHEEL_INCREMENT = 5;
47 private static final double MAX_SHARPNESS_FACTOR = 2;
48 private static final double MAX_COLORFUL_FACTOR = 2;
49 private final LayerListModel model;
50 private final JPopupMenu popup;
51 private SideButton sideButton;
52 private final JCheckBox visibilityCheckbox;
53 final OpacitySlider opacitySlider = new OpacitySlider();
54 private final ArrayList<FilterSlider<?>> sliders = new ArrayList<>();
55
56 /**
57 * Creates a new {@link LayerVisibilityAction}
58 * @param model The list to get the selection from.
59 */
60 public LayerVisibilityAction(LayerListModel model) {
61 this.model = model;
62 popup = new JPopupMenu();
63 // prevent popup close on mouse wheel move
64 popup.addMouseWheelListener(MouseWheelEvent::consume);
65
66 // just to add a border
67 JPanel content = new JPanel();
68 popup.add(content);
69 content.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
70 content.setLayout(new GridBagLayout());
71
72 new ImageProvider("dialogs/layerlist", "visibility").getResource().attachImageIcon(this, true);
73 putValue(SHORT_DESCRIPTION, tr("Change visibility of the selected layer."));
74
75 visibilityCheckbox = new JCheckBox(tr("Show layer"));
76 visibilityCheckbox.addChangeListener(e -> setVisibleFlag(visibilityCheckbox.isSelected()));
77 content.add(visibilityCheckbox, GBC.eop());
78
79 addSlider(content, opacitySlider);
80 addSlider(content, new ColorfulnessSlider());
81 addSlider(content, new GammaFilterSlider());
82 addSlider(content, new SharpnessSlider());
83 }
84
85 private void addSlider(JPanel content, FilterSlider<?> slider) {
86 // wrap to a common content pane to allow for mouse wheel listener on label.
87 JPanel container = new JPanel(new GridBagLayout());
88 container.add(new JLabel(slider.getIcon()), GBC.std().span(1, 2).insets(0, 0, 5, 0));
89 container.add(new JLabel(slider.getLabel()), GBC.eol());
90 container.add(slider, GBC.eol());
91 content.add(container, GBC.eop());
92
93 container.addMouseWheelListener(slider::mouseWheelMoved);
94 sliders.add(slider);
95 }
96
97 void setVisibleFlag(boolean visible) {
98 for (Layer l : model.getSelectedLayers()) {
99 l.setVisible(visible);
100 }
101 updateValues();
102 }
103
104 @Override
105 public void actionPerformed(ActionEvent e) {
106 updateValues();
107 if (e.getSource() == sideButton) {
108 popup.show(sideButton, 0, sideButton.getHeight());
109 } else {
110 // Action can be trigger either by opacity button or by popup menu (in case toggle buttons are hidden).
111 // In that case, show it in the middle of screen (because opacityButton is not visible)
112 popup.show(Main.parent, Main.parent.getWidth() / 2, (Main.parent.getHeight() - popup.getHeight()) / 2);
113 }
114 }
115
116 void updateValues() {
117 List<Layer> layers = model.getSelectedLayers();
118
119 visibilityCheckbox.setEnabled(!layers.isEmpty());
120 boolean allVisible = true;
121 boolean allHidden = true;
122 for (Layer l : layers) {
123 allVisible &= l.isVisible();
124 allHidden &= !l.isVisible();
125 }
126 // TODO: Indicate tristate.
127 visibilityCheckbox.setSelected(allVisible && !allHidden);
128
129 for (FilterSlider<?> slider : sliders) {
130 slider.updateSlider(layers, allHidden);
131 }
132 }
133
134 @Override
135 public boolean supportLayers(List<Layer> layers) {
136 return !layers.isEmpty();
137 }
138
139 @Override
140 public Component createMenuComponent() {
141 return new JMenuItem(this);
142 }
143
144 @Override
145 public void updateEnabledState() {
146 setEnabled(!model.getSelectedLayers().isEmpty());
147 }
148
149 /**
150 * Sets the corresponding side button.
151 * @param sideButton the corresponding side button
152 */
153 public void setCorrespondingSideButton(SideButton sideButton) {
154 this.sideButton = sideButton;
155 }
156
157 /**
158 * This is a slider for a filter value.
159 * @author Michael Zangl
160 *
161 * @param <T> The layer type.
162 */
163 private abstract class FilterSlider<T extends Layer> extends JSlider {
164 private final double minValue;
165 private final double maxValue;
166 private final Class<T> layerClassFilter;
167
168 /**
169 * Create a new filter slider.
170 * @param minValue The minimum value to map to the left side.
171 * @param maxValue The maximum value to map to the right side.
172 * @param layerClassFilter The type of layer influenced by this filter.
173 */
174 FilterSlider(double minValue, double maxValue, Class<T> layerClassFilter) {
175 super(JSlider.HORIZONTAL);
176 this.minValue = minValue;
177 this.maxValue = maxValue;
178 this.layerClassFilter = layerClassFilter;
179 setMaximum(SLIDER_STEPS);
180 int tick = convertFromRealValue(1);
181 setMinorTickSpacing(tick);
182 setMajorTickSpacing(tick);
183 setPaintTicks(true);
184
185 addChangeListener(e -> onStateChanged());
186 }
187
188 /**
189 * Called whenever the state of the slider was changed.
190 * @see #getValueIsAdjusting()
191 * @see #getRealValue()
192 */
193 protected void onStateChanged() {
194 Collection<T> layers = filterLayers(model.getSelectedLayers());
195 for (T layer : layers) {
196 applyValueToLayer(layer);
197 }
198 }
199
200 protected void mouseWheelMoved(MouseWheelEvent e) {
201 e.consume();
202 if (!isEnabled()) {
203 // ignore mouse wheel in disabled state.
204 return;
205 }
206 double rotation = -1 * e.getPreciseWheelRotation();
207 double destinationValue = getValue() + rotation * SLIDER_WHEEL_INCREMENT;
208 if (rotation < 0) {
209 destinationValue = Math.floor(destinationValue);
210 } else {
211 destinationValue = Math.ceil(destinationValue);
212 }
213 setValue(Utils.clamp((int) destinationValue, getMinimum(), getMaximum()));
214 }
215
216 abstract void applyValueToLayer(T layer);
217
218 protected double getRealValue() {
219 return convertToRealValue(getValue());
220 }
221
222 protected double convertToRealValue(int value) {
223 double s = (double) value / SLIDER_STEPS;
224 return s * maxValue + (1-s) * minValue;
225 }
226
227 protected void setRealValue(double value) {
228 setValue(convertFromRealValue(value));
229 }
230
231 protected int convertFromRealValue(double value) {
232 int i = (int) ((value - minValue) / (maxValue - minValue) * SLIDER_STEPS + .5);
233 return Utils.clamp(i, getMinimum(), getMaximum());
234 }
235
236 public abstract ImageIcon getIcon();
237
238 public abstract String getLabel();
239
240 public void updateSlider(List<Layer> layers, boolean allHidden) {
241 Collection<? extends Layer> usedLayers = filterLayers(layers);
242 if (usedLayers.isEmpty() || allHidden) {
243 setEnabled(false);
244 } else {
245 setEnabled(true);
246 updateSliderWhileEnabled(usedLayers, allHidden);
247 }
248 }
249
250 protected Collection<T> filterLayers(List<Layer> layers) {
251 return Utils.filteredCollection(layers, layerClassFilter);
252 }
253
254 protected abstract void updateSliderWhileEnabled(Collection<? extends Layer> usedLayers, boolean allHidden);
255 }
256
257 /**
258 * This slider allows you to change the opacity of a layer.
259 *
260 * @author Michael Zangl
261 * @see Layer#setOpacity(double)
262 */
263 class OpacitySlider extends FilterSlider<Layer> {
264 /**
265 * Creaate a new {@link OpacitySlider}.
266 */
267 OpacitySlider() {
268 super(0, 1, Layer.class);
269 setToolTipText(tr("Adjust opacity of the layer."));
270 }
271
272 @Override
273 protected void onStateChanged() {
274 if (getRealValue() <= 0.001 && !getValueIsAdjusting()) {
275 setVisibleFlag(false);
276 } else {
277 super.onStateChanged();
278 }
279 }
280
281 @Override
282 protected void mouseWheelMoved(MouseWheelEvent e) {
283 if (!isEnabled() && !filterLayers(model.getSelectedLayers()).isEmpty() && e.getPreciseWheelRotation() < 0) {
284 // make layer visible and set the value.
285 // this allows users to use the mouse wheel to make the layer visible if it was hidden previously.
286 e.consume();
287 setVisibleFlag(true);
288 } else {
289 super.mouseWheelMoved(e);
290 }
291 }
292
293 @Override
294 protected void applyValueToLayer(Layer layer) {
295 layer.setOpacity(getRealValue());
296 }
297
298 @Override
299 protected void updateSliderWhileEnabled(Collection<? extends Layer> usedLayers, boolean allHidden) {
300 double opacity = 0;
301 for (Layer l : usedLayers) {
302 opacity += l.getOpacity();
303 }
304 opacity /= usedLayers.size();
305 if (opacity == 0) {
306 opacity = 1;
307 setVisibleFlag(true);
308 }
309 setRealValue(opacity);
310 }
311
312 @Override
313 public String getLabel() {
314 return tr("Opacity");
315 }
316
317 @Override
318 public ImageIcon getIcon() {
319 return ImageProvider.get("dialogs/layerlist", "transparency");
320 }
321
322 @Override
323 public String toString() {
324 return "OpacitySlider [getRealValue()=" + getRealValue() + ']';
325 }
326 }
327
328 /**
329 * This slider allows you to change the gamma value of a layer.
330 *
331 * @author Michael Zangl
332 * @see ImageryFilterSettings#setGamma(double)
333 */
334 private class GammaFilterSlider extends FilterSlider<ImageryLayer> {
335
336 /**
337 * Create a new {@link GammaFilterSlider}
338 */
339 GammaFilterSlider() {
340 super(-1, 1, ImageryLayer.class);
341 setToolTipText(tr("Adjust gamma value of the layer."));
342 }
343
344 @Override
345 protected void updateSliderWhileEnabled(Collection<? extends Layer> usedLayers, boolean allHidden) {
346 double gamma = ((ImageryLayer) usedLayers.iterator().next()).getFilterSettings().getGamma();
347 setRealValue(mapGammaToInterval(gamma));
348 }
349
350 @Override
351 protected void applyValueToLayer(ImageryLayer layer) {
352 layer.getFilterSettings().setGamma(mapIntervalToGamma(getRealValue()));
353 }
354
355 @Override
356 public ImageIcon getIcon() {
357 return ImageProvider.get("dialogs/layerlist", "gamma");
358 }
359
360 @Override
361 public String getLabel() {
362 return tr("Gamma");
363 }
364
365 /**
366 * Maps a number x from the range (-1,1) to a gamma value.
367 * Gamma value is in the range (0, infinity).
368 * Gamma values of 3 and 1/3 have opposite effects, so the mapping
369 * should be symmetric in that sense.
370 * @param x the slider value in the range (-1,1)
371 * @return the gamma value
372 */
373 private double mapIntervalToGamma(double x) {
374 // properties of the mapping:
375 // g(-1) = 0
376 // g(0) = 1
377 // g(1) = infinity
378 // g(-x) = 1 / g(x)
379 return (1 + x) / (1 - x);
380 }
381
382 private double mapGammaToInterval(double gamma) {
383 return (gamma - 1) / (gamma + 1);
384 }
385 }
386
387 /**
388 * This slider allows you to change the sharpness of a layer.
389 *
390 * @author Michael Zangl
391 * @see ImageryFilterSettings#setSharpenLevel(double)
392 */
393 private class SharpnessSlider extends FilterSlider<ImageryLayer> {
394
395 /**
396 * Creates a new {@link SharpnessSlider}
397 */
398 SharpnessSlider() {
399 super(0, MAX_SHARPNESS_FACTOR, ImageryLayer.class);
400 setToolTipText(tr("Adjust sharpness/blur value of the layer."));
401 }
402
403 @Override
404 protected void updateSliderWhileEnabled(Collection<? extends Layer> usedLayers, boolean allHidden) {
405 setRealValue(((ImageryLayer) usedLayers.iterator().next()).getFilterSettings().getSharpenLevel());
406 }
407
408 @Override
409 protected void applyValueToLayer(ImageryLayer layer) {
410 layer.getFilterSettings().setSharpenLevel(getRealValue());
411 }
412
413 @Override
414 public ImageIcon getIcon() {
415 return ImageProvider.get("dialogs/layerlist", "sharpness");
416 }
417
418 @Override
419 public String getLabel() {
420 return tr("Sharpness");
421 }
422 }
423
424 /**
425 * This slider allows you to change the colorfulness of a layer.
426 *
427 * @author Michael Zangl
428 * @see ImageryFilterSettings#setColorfulness(double)
429 */
430 private class ColorfulnessSlider extends FilterSlider<ImageryLayer> {
431
432 /**
433 * Create a new {@link ColorfulnessSlider}
434 */
435 ColorfulnessSlider() {
436 super(0, MAX_COLORFUL_FACTOR, ImageryLayer.class);
437 setToolTipText(tr("Adjust colorfulness of the layer."));
438 }
439
440 @Override
441 protected void updateSliderWhileEnabled(Collection<? extends Layer> usedLayers, boolean allHidden) {
442 setRealValue(((ImageryLayer) usedLayers.iterator().next()).getFilterSettings().getColorfulness());
443 }
444
445 @Override
446 protected void applyValueToLayer(ImageryLayer layer) {
447 layer.getFilterSettings().setColorfulness(getRealValue());
448 }
449
450 @Override
451 public ImageIcon getIcon() {
452 return ImageProvider.get("dialogs/layerlist", "colorfulness");
453 }
454
455 @Override
456 public String getLabel() {
457 return tr("Colorfulness");
458 }
459 }
460}
Note: See TracBrowser for help on using the repository browser.