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

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

sonar - squid:S2156 - "final" classes should not have "protected" members

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