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

Last change on this file since 10428 was 10428, checked in by stoecker, 8 years ago

see #9995 - patch mainly by strump - improve HIDPI behaviour

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