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

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

fix #13159 - Move image processors out of imagery layer (patch by michael2402) - gsoc-core + fix checkstyle violations

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