001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.tagging.presets.items;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005
006import java.awt.GridBagLayout;
007import java.util.ArrayList;
008import java.util.List;
009import java.util.Set;
010
011import javax.swing.JLabel;
012import javax.swing.JPanel;
013
014import org.openstreetmap.josm.data.osm.Tag;
015import org.openstreetmap.josm.data.osm.search.SearchCompiler;
016import org.openstreetmap.josm.data.osm.search.SearchParseError;
017import org.openstreetmap.josm.data.osm.search.SearchSetting;
018import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItem;
019import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemGuiSupport;
020import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetType;
021import org.openstreetmap.josm.tools.GBC;
022import org.openstreetmap.josm.tools.ImageProvider;
023import org.xml.sax.SAXException;
024
025/**
026 * The <code>roles</code> element in tagging presets definition.
027 * <p>
028 * A list of {@link Role} elements. Describes the roles that are expected for
029 * the members of a relation.
030 * <p>
031 * Used for data validation, auto completion, among others.
032 */
033public class Roles extends TaggingPresetItem {
034
035    /**
036     * The <code>role</code> element in tagging preset definition.
037     *
038     * Information on a certain role, which is expected for the relation members.
039     */
040    public static class Role {
041        /** Presets types expected for this role */
042        public Set<TaggingPresetType> types; // NOSONAR
043        /** Role name used in a relation */
044        public String key; // NOSONAR
045        /** Is the role name a regular expression */
046        public boolean regexp; // NOSONAR
047        /** The text to display */
048        public String text; // NOSONAR
049        /** The context used for translating {@link #text} */
050        public String text_context; // NOSONAR
051        /** The localized version of {@link #text}. */
052        public String locale_text; // NOSONAR
053        /** An expression (cf. search dialog) for objects of this role */
054        public SearchCompiler.Match memberExpression; // NOSONAR
055        /** Is this role required at least once in the relation? */
056        public boolean required; // NOSONAR
057        /** How often must the element appear */
058        private short count;
059
060        /**
061         * Sets the presets types expected for this role.
062         * @param types comma-separated set of expected types
063         * @throws SAXException if an unknown type is detected
064         */
065        public void setType(String types) throws SAXException {
066            this.types = getType(types);
067        }
068
069        /**
070         * Sets whether this role is required at least once in the relation.
071         * @param str "required" or "optional"
072         * @throws SAXException if str is neither "required" or "optional"
073         */
074        public void setRequisite(String str) throws SAXException {
075            if ("required".equals(str)) {
076                required = true;
077            } else if (!"optional".equals(str))
078                throw new SAXException(tr("Unknown requisite: {0}", str));
079        }
080
081        /**
082         * Sets whether the role name is a regular expression.
083         * @param str "true" or "false"
084         * @throws SAXException if str is neither "true" or "false"
085         */
086        public void setRegexp(String str) throws SAXException {
087            if ("true".equals(str)) {
088                regexp = true;
089            } else if (!"false".equals(str))
090                throw new SAXException(tr("Unknown regexp value: {0}", str));
091        }
092
093        /**
094         * Sets an expression (cf. search dialog) for objects of this role
095         * @param memberExpression an expression (cf. search dialog) for objects of this role
096         * @throws SAXException in case of parsing error
097         */
098        public void setMember_expression(String memberExpression) throws SAXException {
099            try {
100                final SearchSetting searchSetting = new SearchSetting();
101                searchSetting.text = memberExpression;
102                searchSetting.caseSensitive = true;
103                searchSetting.regexSearch = true;
104                this.memberExpression = SearchCompiler.compile(searchSetting);
105            } catch (SearchParseError ex) {
106                throw new SAXException(tr("Illegal member expression: {0}", ex.getMessage()), ex);
107            }
108        }
109
110        /**
111         * Sets how often must the element appear.
112         * @param count how often must the element appear
113         */
114        public void setCount(String count) {
115            this.count = Short.parseShort(count);
116        }
117
118        /**
119         * Return either argument, the highest possible value or the lowest allowed value
120         * @param c count
121         * @return the highest possible value or the lowest allowed value
122         * @see #required
123         */
124        public long getValidCount(long c) {
125            if (count > 0 && !required)
126                return c != 0 ? count : 0;
127            else if (count > 0)
128                return count;
129            else if (!required)
130                return c != 0 ? c : 0;
131            else
132                return c != 0 ? c : 1;
133        }
134
135        /**
136         * Check if the given role matches this class (required to check regexp role types)
137         * @param role role to check
138         * @return <code>true</code> if role matches
139         * @since 11989
140         */
141        public boolean isRole(String role) {
142            if (regexp && role != null) { // pass null through, it will anyway fail
143                return role.matches(this.key);
144            }
145            return this.key.equals(role);
146        }
147
148        /**
149         * Adds this role to the given panel.
150         * @param p panel where to add this role
151         * @return {@code true}
152         */
153        public boolean addToPanel(JPanel p) {
154            String cstring;
155            if (count > 0 && !required) {
156                cstring = "0,"+count;
157            } else if (count > 0) {
158                cstring = String.valueOf(count);
159            } else if (!required) {
160                cstring = "0-...";
161            } else {
162                cstring = "1-...";
163            }
164            if (locale_text == null) {
165                locale_text = getLocaleText(text, text_context, null);
166            }
167            p.add(new JLabel(locale_text+':'), GBC.std().insets(0, 0, 10, 0));
168            p.add(new JLabel(key), GBC.std().insets(0, 0, 10, 0));
169            p.add(new JLabel(cstring), types == null ? GBC.eol() : GBC.std().insets(0, 0, 10, 0));
170            if (types != null) {
171                JPanel pp = new JPanel();
172                for (TaggingPresetType t : types) {
173                    pp.add(new JLabel(ImageProvider.get(t.getIconName())));
174                }
175                p.add(pp, GBC.eol());
176            }
177            return true;
178        }
179
180        @Override
181        public String toString() {
182            return "Role [key=" + key + ", text=" + text + ']';
183        }
184    }
185
186    /**
187     * List of {@link Role} elements.
188     */
189    public final List<Role> roles = new ArrayList<>(2);
190
191    @Override
192    public boolean addToPanel(JPanel p, TaggingPresetItemGuiSupport support) {
193        p.add(new JLabel(" "), GBC.eol()); // space
194        if (!roles.isEmpty()) {
195            JPanel proles = new JPanel(new GridBagLayout());
196            proles.add(new JLabel(tr("Available roles")), GBC.std().insets(0, 0, 10, 0));
197            proles.add(new JLabel(tr("role")), GBC.std().insets(0, 0, 10, 0));
198            proles.add(new JLabel(tr("count")), GBC.std().insets(0, 0, 10, 0));
199            proles.add(new JLabel(tr("elements")), GBC.eol());
200            for (Role i : roles) {
201                i.addToPanel(proles);
202            }
203            proles.applyComponentOrientation(support.getDefaultComponentOrientation());
204            p.add(proles, GBC.eol());
205        }
206        return false;
207    }
208
209    @Override
210    public void addCommands(List<Tag> changedTags) {
211        // Do nothing
212    }
213
214    @Override
215    public String toString() {
216        return "Roles [roles=" + roles + ']';
217    }
218}