/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2002-2008, Open Source Geospatial Foundation (OSGeo) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package org.geotools.swing.styling; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.LineString; import com.vividsolutions.jts.geom.MultiLineString; import com.vividsolutions.jts.geom.MultiPoint; import com.vividsolutions.jts.geom.MultiPolygon; import com.vividsolutions.jts.geom.Point; import com.vividsolutions.jts.geom.Polygon; import java.awt.Color; import java.awt.Component; import java.awt.Dialog; import java.awt.Frame; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import javax.swing.DefaultComboBoxModel; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JColorChooser; import javax.swing.JComboBox; import javax.swing.JDialog; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JSlider; import javax.swing.MutableComboBoxModel; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import net.miginfocom.swing.MigLayout; import org.geotools.data.AbstractDataStore; import org.geotools.factory.CommonFactoryFinder; import org.geotools.map.MapLayer; import org.geotools.styling.FeatureTypeStyle; import org.geotools.styling.Font; import org.geotools.styling.LineSymbolizer; import org.geotools.styling.PointSymbolizer; import org.geotools.styling.PolygonSymbolizer; import org.geotools.styling.Rule; import org.geotools.styling.SLD; import org.geotools.styling.Style; import org.geotools.styling.StyleFactory; import org.geotools.styling.Symbolizer; import org.geotools.swing.utils.MapLayerUtils; import org.opengis.feature.simple.SimpleFeatureType; import org.opengis.feature.type.AttributeDescriptor; import org.opengis.feature.type.GeometryDescriptor; import org.opengis.filter.expression.Expression; /** * A dialog to prompt the user for feature style choices. It has a number of static * {@code showDialog} methods to work with different sources ({@code SimpleFeatureType}, * {@code MapLayer}, {@code DataStore}. Each of these displays a dialog and then creates * a new {@code Style} instance. * <p> * Examples of use: * <pre><code> * // Use with a shapefile * Component parentGUIComponent = null; * ShapefileDataStore shapefile = ... * Style style = JSimpleStyleDialog.showDialog(parentGUIComponent, shapefile); * if (style != null) { * // create a map layer using this style * } * * // Use with an existing MapLayer * MapLayer layer = ... * Style style = JSimpleStyleDialog.showDialog(parentGUIComponent, layer); * if (style != null) { * layer.setStyle( style ); * } * </code></pre> * * @see SLD SLD style helper class * * @author Michael Bedward * @since 2.6 * @source $URL$ * @version $Id$ */ public class JSimpleStyleDialog extends JDialog { private static StyleFactory sf = CommonFactoryFinder.getStyleFactory(null); /** * Well known text names for symbol options * @todo these must be defined somwhere else ? */ public static final String WELL_KNOWN_SYMBOL_NAMES[] = {"Circle", "Square", "Cross", "X", "Triangle", "Star"}; public static final Color DEFAULT_LINE_COLOR = Color.BLACK; public static final Color DEFAULT_FILL_COLOR = Color.WHITE; public static final float DEFAULT_LINE_WIDTH = 1.0f; public static final float DEFAULT_OPACITY = 1.0f; public static final float DEFAULT_POINT_SIZE = 3.0f; public static final String DEFAULT_POINT_SYMBOL_NAME = "Circle"; private static int COLOR_ICON_SIZE = 16; /** * Constants for the geometry type that the style * preferences apply to */ public static enum GeomType { UNDEFINED("undefined type", (Class<? extends Geometry>)null), POINT("point", Point.class), LINE("line", LineString.class, MultiLineString.class), POLYGON("polygon", Polygon.class, MultiPolygon.class); private String desc; private Set<Class<? extends Geometry>> classes; /** * Private constructor. * * @param desc brief description for {@code toString} method * @param classes {@code Geometry} classes that this type corresponds to */ private GeomType(String desc, Class<? extends Geometry> ...classes) { this.desc = desc; this.classes = new HashSet< Class<? extends Geometry> >(); if (classes != null) { for (Class<? extends Geometry> clazz : classes) { this.classes.add(clazz); } } } /** * Get the {@code Geometry} classes that correspond to this type * @return an unmodifiable set of classes */ public Set<Class<? extends Geometry>> getClasses() { return Collections.unmodifiableSet(classes); } @Override public String toString() { return desc; } } private GeomType geomType; private static enum SourceType { DATA_STORE, MAP_LAYER; } private Color lineColor; private Color fillColor; private float lineWidth; private float opacity; private float pointSize; private String pointSymbolName; private boolean labelFeatures; private String labelField; private Font labelFont; private JColorIcon lineColorIcon; private JLabel lineColorLabel; private JColorIcon fillColorIcon; private JLabel fillColorLabel; private JSlider fillOpacitySlider; private JComboBox pointSizeCBox; private JComboBox pointSymbolCBox; private JLabel typeLabel; private JComboBox labelCBox; private static enum ControlCategory { LINE, FILL, POINT; } private Map<Component, ControlCategory> controls; private final SimpleFeatureType schema; private String[] fieldsForLabels; private boolean completed; /** * Static convenience method: displays a {@code JSimpleStyleDialog} to prompt * the user for style preferences to use with the given {@code MapLayer}. The * layer's existing style, if any, will be used to initialize the dialog. * * @param parent parent component (may be null) * @param layer the map layer * * @return a new Style instance or null if the user cancels the dialog */ public static Style showDialog(Component parent, MapLayer layer) { /* * Grid coverages and readers are not supported yet... */ if (MapLayerUtils.isGridLayer(layer)) { JOptionPane.showMessageDialog(null, "Sorry, styling for for grid coverages is not working yet", "Style dialog", JOptionPane.WARNING_MESSAGE); return null; } SimpleFeatureType type = (SimpleFeatureType) layer.getFeatureSource().getSchema(); return showDialog(parent, type, layer.getStyle()); } /** * Static convenience method: displays a {@code JSimpleStyleDialog} to prompt * the user for style preferences to use with the first feature type in * the {@code dataStore}. * * @param parent parent JFrame (may be null) * @param dataStore data store with the features to be rendered * * @return a new Style instance or null if the user cancels the dialog */ public static Style showDialog(Component parent, AbstractDataStore dataStore) { return showDialog(parent, dataStore, (Style) null); } /** * Static convenience method: displays a {@code JSimpleStyleDialog} to prompt * the user for style preferences to use with the first feature type in * the {@code dataStore}. * * @param parent parent JFrame (may be null) * @param dataStore data store with the features to be rendered * @param initialStyle an optional Style object to initialize the dialog * (may be {@code null}) * * @return a new Style instance or null if the user cancels the dialog */ public static Style showDialog(Component parent, AbstractDataStore dataStore, Style initialStyle) { SimpleFeatureType type = null; try { String typeName = dataStore.getTypeNames()[0]; type = dataStore.getSchema(typeName); } catch (Exception ex) { throw new IllegalStateException(ex); } return showDialog(parent, type, initialStyle); } /** * Static convenience method: displays a {@code JSimpleStyleDialog} to prompt * the user for style preferences to use with the given feature type. * * @param parent parent component (may be null) * @param featureType the feature type that the Style will be used to display * * @return a new Style instance or null if the user cancels the dialog */ public static Style showDialog(Component parent, SimpleFeatureType featureType) { return showDialog(parent, featureType, (Style) null); } /** * Static convenience method: displays a {@code JSimpleStyleDialog} to prompt * the user for style preferences to use with the given feature type. * * @param parent parent component (may be null) * @param featureType the feature type that the Style will be used to display * @param initialStyle an optional Style object to initialize the dialog * (may be {@code null}) * * @return a new Style instance or null if the user cancels the dialog */ public static Style showDialog(Component parent, SimpleFeatureType featureType, Style initialStyle) { Style style = null; JSimpleStyleDialog dialog = null; if (parent != null) { if (parent instanceof Frame) { dialog = new JSimpleStyleDialog((Frame) parent, featureType, initialStyle); } else if (parent instanceof Dialog) { dialog = new JSimpleStyleDialog((Dialog) parent, featureType, initialStyle); } } if (dialog == null) { dialog = new JSimpleStyleDialog((Frame)null, featureType, initialStyle); } dialog.setVisible(true); if (dialog.completed()) { switch (dialog.getGeomType()) { case POLYGON: style = SLD.createPolygonStyle( dialog.getLineColor(), dialog.getFillColor(), dialog.getOpacity(), dialog.getLabelField(), dialog.getLabelFont()); break; case LINE: style = SLD.createLineStyle( dialog.getLineColor(), dialog.getLineWidth(), dialog.getLabelField(), dialog.getLabelFont()); break; case POINT: style = SLD.createPointStyle( dialog.getPointSymbolName(), dialog.getLineColor(), dialog.getFillColor(), dialog.getOpacity(), dialog.getPointSize(), dialog.getLabelField(), dialog.getLabelFont()); break; } } dialog.dispose(); return style; } /** * Constructor. * * @param owner the parent Frame (may be null) * @param schema the feature type for which the style is being created * @param initialStyle an optional Style object to initialize the dialog * (may be {@code null}) * * @throws IllegalStateException if the data store cannot be accessed */ public JSimpleStyleDialog(Frame owner, SimpleFeatureType schema, Style initialStyle) { super(owner, "Simple style maker", true); setResizable(false); this.schema = schema; init(initialStyle); } /** * Constructor. * * @param owner the parent Dialog (may be null) * @param schema the feature type for which the style is being created * @param initialStyle an optional Style object to initialize the dialog * (may be {@code null}) * * @throws IllegalStateException if the data store cannot be accessed */ public JSimpleStyleDialog(Dialog owner, SimpleFeatureType schema, Style initialStyle) { super(owner, "Simple style maker", true); setResizable(false); this.schema = schema; init(initialStyle); } /** * Helper for constructors * * @param initialStyle an optional Style object to initialize the dialog * (may be {@code null}) */ private void init(Style initialStyle) { lineColor = DEFAULT_LINE_COLOR; fillColor = DEFAULT_FILL_COLOR; lineWidth = DEFAULT_LINE_WIDTH; opacity = DEFAULT_OPACITY; pointSize = DEFAULT_POINT_SIZE; pointSymbolName = DEFAULT_POINT_SYMBOL_NAME; labelFeatures = false; labelField = null; labelFont = sf.getDefaultFont(); geomType = GeomType.UNDEFINED; completed = false; try { initComponents(); setType(); setStyle(initialStyle); } catch (Exception ex) { throw new IllegalStateException(ex); } } /** * Query if the dialog was completed (user clicked the Apply button) * @return true if completed; false otherwise */ public boolean completed() { return completed; } /** * Get the {@linkplain GeomType} constant for the selected * feature type. If the user cancelled the dialog this will * be {@linkplain GeomType#UNDEFINED}. * * @return GeomType constant */ public GeomType getGeomType() { return geomType; } /** * Get the selected line color * * @return line color */ public Color getLineColor() { return lineColor; } /** * Get the selected fill color * * @return fill color */ public Color getFillColor() { return fillColor; } /** * Get the fill opacity * * @return fill opacity between 0 and 1 */ public float getOpacity() { return opacity; } /** * Get the selected line width * * @return line width */ public float getLineWidth() { return lineWidth; } /** * Get the selected point size * * @return point size */ public float getPointSize() { return pointSize; } /** * Get the selected point symbol name * * @return symbol name */ public String getPointSymbolName() { return pointSymbolName; } /** * Get the feature field (attribute) to use for * labels * * @return field name */ public String getLabelField() { if (labelFeatures) { return labelField; } return null; } /** * Get the font to use for labels * * @return a GeoTools Font object */ public Font getLabelFont() { return labelFont; } /** * Create and layout the controls */ private void initComponents() { MigLayout layout = new MigLayout(); JPanel panel = new JPanel(layout); controls = new HashMap<Component, ControlCategory>(); JLabel label = null; JButton btn = null; label = new JLabel("Feature type"); label.setForeground(Color.BLUE); panel.add(label, "wrap"); typeLabel = new JLabel(); panel.add(typeLabel, "gapbefore indent, span, wrap"); /* * Line style items */ label = new JLabel("Line"); label.setForeground(Color.BLUE); panel.add(label, "wrap"); btn = new JButton("Color..."); btn.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { chooseLineColor(); } }); panel.add(btn, "gapbefore indent"); controls.put(btn, ControlCategory.LINE); lineColorIcon = new JColorIcon(COLOR_ICON_SIZE, COLOR_ICON_SIZE, DEFAULT_LINE_COLOR); lineColorLabel = new JLabel(lineColorIcon); panel.add(lineColorLabel, "gapafter 20px"); label = new JLabel("Width"); panel.add(label, "split 2"); Object[] widths = new Object[5]; for (int i = 1; i <= widths.length; i++) { widths[i-1] = Integer.valueOf(i); } final JComboBox lineWidthCBox = new JComboBox(widths); lineWidthCBox.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { lineWidth = ((Number)lineWidthCBox.getModel().getSelectedItem()).intValue(); } }); panel.add(lineWidthCBox, "wrap"); controls.put(lineWidthCBox, ControlCategory.LINE); /* * Fill style items */ label = new JLabel("Fill"); label.setForeground(Color.BLUE); panel.add(label, "wrap"); btn = new JButton("Color..."); btn.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { chooseFillColor(); } }); panel.add(btn, "gapbefore indent"); controls.put(btn, ControlCategory.FILL); fillColorIcon = new JColorIcon(COLOR_ICON_SIZE, COLOR_ICON_SIZE, DEFAULT_FILL_COLOR); fillColorLabel = new JLabel(fillColorIcon); panel.add(fillColorLabel, "gapafter 20px"); label = new JLabel("% opacity"); panel.add(label, "split 2"); fillOpacitySlider = new JSlider(0, 100, 100); fillOpacitySlider.setPaintLabels(true); fillOpacitySlider.setMajorTickSpacing(20); fillOpacitySlider.addChangeListener(new ChangeListener() { public void stateChanged(ChangeEvent e) { opacity = (float)fillOpacitySlider.getValue() / 100; } }); panel.add(fillOpacitySlider, "span, wrap"); controls.put(fillOpacitySlider, ControlCategory.FILL); /* * Point style items */ label = new JLabel("Point"); label.setForeground(Color.BLUE); panel.add(label, "wrap"); label = new JLabel("Size"); panel.add(label, "gapbefore indent, split 2"); Object[] sizes = new Object[10]; for (int i = 1; i <= sizes.length; i++) { sizes[i-1] = Integer.valueOf(i*5); } pointSizeCBox = new JComboBox(sizes); pointSizeCBox.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { pointSize = ((Number)pointSizeCBox.getModel().getSelectedItem()).intValue(); } }); panel.add(pointSizeCBox); controls.put(pointSizeCBox, ControlCategory.POINT); label = new JLabel("Symbol"); panel.add(label, "skip, split 2"); final Object[] marks = new Object[ WELL_KNOWN_SYMBOL_NAMES.length ]; for (int i = 0; i < marks.length; i++) { marks[i] = WELL_KNOWN_SYMBOL_NAMES[i]; } pointSymbolCBox = new JComboBox(marks); pointSymbolCBox.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { pointSymbolName = marks[pointSymbolCBox.getSelectedIndex()].toString(); } }); panel.add(pointSymbolCBox, "wrap"); controls.put(pointSymbolCBox, ControlCategory.POINT); /* * Label items */ label = new JLabel("Labels"); label.setForeground(Color.BLUE); panel.add(label, "wrap"); final JButton fontBtn = new JButton("Font..."); fontBtn.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { chooseLabelFont(); } }); labelCBox = new JComboBox(); labelCBox.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { labelField = labelCBox.getModel().getSelectedItem().toString(); } }); final JCheckBox checkBox = new JCheckBox(); checkBox.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { labelFeatures = checkBox.isSelected(); labelCBox.setEnabled(labelFeatures); fontBtn.setEnabled(labelFeatures); } }); panel.add(checkBox, "gapbefore indent, span, split 3"); label = new JLabel("Field"); panel.add(label); labelCBox.setEnabled(checkBox.isSelected()); panel.add(labelCBox, "wrap"); fontBtn.setEnabled(checkBox.isSelected()); panel.add(fontBtn, "wrap"); /* * Apply and Cancel buttons */ btn = new JButton("Apply"); btn.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { completed = true; setVisible(false); } }); panel.add(btn, "span, split 2, align right"); btn = new JButton("Cancel"); btn.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { geomType = GeomType.UNDEFINED; setVisible(false); } }); panel.add(btn); getContentPane().add(panel); pack(); } /** * Set up the dialog to work with a given feature type */ private void setType() { GeometryDescriptor desc = schema.getGeometryDescriptor(); Class<?> clazz = desc.getType().getBinding(); String labelText = schema.getTypeName(); if (Polygon.class.isAssignableFrom(clazz) || MultiPolygon.class.isAssignableFrom(clazz)) { geomType = GeomType.POLYGON; labelText = labelText + " (polygon)"; } else if (LineString.class.isAssignableFrom(clazz) || MultiLineString.class.isAssignableFrom(clazz)) { geomType = GeomType.LINE; labelText = labelText + " (line)"; } else if (Point.class.isAssignableFrom(clazz) || MultiPoint.class.isAssignableFrom(clazz)) { geomType = GeomType.POINT; labelText = labelText + " (point)"; } else { throw new UnsupportedOperationException("No style method for " + clazz.getName()); } typeLabel.setText(labelText); // enable relevant components for (Component c : controls.keySet()) { switch (controls.get(c)) { case LINE: // nothing to do at present break; case FILL: c.setEnabled(geomType != GeomType.LINE); break; case POINT: c.setEnabled(geomType == GeomType.POINT); break; } } // set the fields available for labels fieldsForLabels = new String[schema.getAttributeCount() - 1]; int k = 0; for (AttributeDescriptor attr : schema.getAttributeDescriptors()) { if ( Geometry.class.isAssignableFrom( attr.getType().getBinding() ) ) { continue; } fieldsForLabels[k++] = attr.getLocalName(); } labelCBox.setModel(new DefaultComboBoxModel(fieldsForLabels)); } /** * Set dialog items to show the contents of the given style * * @param style style to display */ private void setStyle(Style style) { assert(geomType != GeomType.UNDEFINED); FeatureTypeStyle featureTypeStyle = null; Rule rule = null; Symbolizer symbolizer = null; if (style != null) { featureTypeStyle = SLD.featureTypeStyle(style, schema); if (featureTypeStyle != null) { /* * At present this dialog just examines the very first rule and symbolizer */ if (featureTypeStyle.rules() == null || featureTypeStyle.rules().isEmpty()) { return; } rule = featureTypeStyle.rules().get(0); if (rule.symbolizers() == null) { return; } for (Symbolizer sym : rule.symbolizers()) { if (isValidSymbolizer(sym, geomType)) { symbolizer = sym; break; } } if (symbolizer == null) { return; } } else { /* * Just grap the first feature type style that contains the * right sort of symbolizer */ for (int ifts = 0; featureTypeStyle == null && ifts < style.featureTypeStyles().size(); ifts++) { FeatureTypeStyle fts = style.featureTypeStyles().get(ifts); for (int irule = 0; featureTypeStyle == null && irule < fts.rules().size(); irule++) { Rule r = fts.rules().get(irule); for (Symbolizer sym : r.symbolizers()) { if (isValidSymbolizer(sym, geomType)) { featureTypeStyle = fts; rule = r; symbolizer = sym; break; } } } } } if (featureTypeStyle != null && rule != null && symbolizer != null) { initControls(featureTypeStyle, rule, symbolizer); } } } /** * Initialize the control states based on the given style objects * * @param fts a {@code FeatureTypeStyle} * @param rule a {@code Rule} * @param sym a {@code Symbolizer} */ private void initControls(FeatureTypeStyle fts, Rule rule, Symbolizer sym) { Expression exp = null; switch (geomType) { case POLYGON: PolygonSymbolizer polySym = (PolygonSymbolizer) sym; setLineColorItems( SLD.color(polySym.getStroke()) ); setFillColorItems( SLD.color(polySym.getFill()) ); setFillOpacityItems( SLD.opacity(polySym.getFill()) ); break; case LINE: LineSymbolizer lineSym = (LineSymbolizer) sym; setLineColorItems( SLD.color(lineSym) ); break; case POINT: PointSymbolizer pointSym = (PointSymbolizer) sym; setLineColorItems( SLD.pointColor(pointSym) ); setFillColorItems( SLD.pointFill(pointSym) ); setFillOpacityItems( SLD.pointOpacity(pointSym) ); setPointSizeItems( SLD.pointSize(pointSym) ); setPointSymbolItems( SLD.pointWellKnownName(pointSym) ); } } private boolean isValidSymbolizer(Symbolizer sym, GeomType type) { if (sym != null) { if (sym instanceof PolygonSymbolizer) { return type == GeomType.POLYGON; } else if (sym instanceof LineSymbolizer) { return type == GeomType.LINE; } else if (sym instanceof PointSymbolizer) { return type == GeomType.POINT; } } return false; } /** * Display a color chooser dialog to set the line color */ private void chooseLineColor() { Color color = JColorChooser.showDialog(this, "Choose line color", lineColor); setLineColorItems(color); } /** * Set the line color items to show the given color choice * @param color current color */ private void setLineColorItems(Color color) { if (color != null) { lineColor = color; lineColorIcon.setColor(color); lineColorLabel.repaint(); } } /** * Display a color chooser dialog to set the fill color */ private void chooseFillColor() { Color color = JColorChooser.showDialog(this, "Choose fill color", fillColor); setFillColorItems(color); } /** * Set the fill color items to show the given color choice * @param color current color */ private void setFillColorItems(Color color) { if (color != null) { fillColor = color; fillColorIcon.setColor(color); fillColorLabel.repaint(); } } /** * Set the fill opacity items to the given value * @param value opacity value between 0 and 1 */ private void setFillOpacityItems(double value) { opacity = (float) Math.min(1.0, Math.max(0.0, value)); fillOpacitySlider.setValue((int)(opacity * 100)); } /** * Set items for the given point size * * @param value point size */ private void setPointSizeItems(double value) { pointSize = (float) Math.max(0.0, value); int newValue = (int)pointSize; MutableComboBoxModel model = (MutableComboBoxModel) pointSizeCBox.getModel(); int insert = -1; for (int i = 0; i < model.getSize(); i++) { int elValue = ((Number)model.getElementAt(i)).intValue(); if (elValue == newValue) { pointSizeCBox.setSelectedIndex(i); return; } else if (elValue > newValue) { insert = i; break; } } if (insert < 0) { insert = model.getSize(); model.addElement(Integer.valueOf(newValue)); } else { model.insertElementAt(Integer.valueOf(newValue), insert); } pointSizeCBox.setSelectedIndex(insert); } /** * Set items for the given point symbol, identified by its 'well known name' * * @param wellKnownName name of the symbol */ private void setPointSymbolItems(String wellKnownName) { if (wellKnownName != null) { for (int i = 0; i < WELL_KNOWN_SYMBOL_NAMES.length; i++) { if (WELL_KNOWN_SYMBOL_NAMES[i].equalsIgnoreCase(wellKnownName)) { pointSymbolName = WELL_KNOWN_SYMBOL_NAMES[i]; pointSymbolCBox.setSelectedIndex(i); break; } } } } private void chooseLabelFont() { Font font = JFontChooser.showDialog(this, "Choose label font", labelFont); if (font != null) { labelFont = font; } } }