source: osm/applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/multitagger/MultiTagDialog.java@ 35682

Last change on this file since 35682 was 35682, checked in by GerdP, 4 years ago

see #17184: Memory Leaks

  • don't keep dialog in MultiTagAction, it is newly created each time when the shortcut is pressed
  • overwrite setVisible to clear tableModel in MultiTagDialog so that it doesn't keep refs to OSM objects
  • simplify code (sonar issues)
File size: 14.9 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.plugins.utilsplugin2.multitagger;
3
4import static org.openstreetmap.josm.tools.I18n.marktr;
5import static org.openstreetmap.josm.tools.I18n.tr;
6
7import java.awt.Color;
8import java.awt.Component;
9import java.awt.Dimension;
10import java.awt.GridBagLayout;
11import java.awt.event.ActionEvent;
12import java.awt.event.ItemEvent;
13import java.awt.event.ItemListener;
14import java.awt.event.KeyEvent;
15import java.awt.event.MouseAdapter;
16import java.awt.event.MouseEvent;
17import java.util.ArrayList;
18import java.util.Arrays;
19import java.util.Collection;
20import java.util.Collections;
21import java.util.LinkedList;
22import java.util.List;
23
24import javax.swing.AbstractAction;
25import javax.swing.BoxLayout;
26import javax.swing.JButton;
27import javax.swing.JComponent;
28import javax.swing.JLabel;
29import javax.swing.JPanel;
30import javax.swing.JPopupMenu;
31import javax.swing.JScrollPane;
32import javax.swing.JTable;
33import javax.swing.JToggleButton;
34import javax.swing.KeyStroke;
35import javax.swing.ListSelectionModel;
36import javax.swing.UIManager;
37import javax.swing.event.ListSelectionListener;
38import javax.swing.table.DefaultTableCellRenderer;
39
40import org.openstreetmap.josm.actions.AutoScaleAction;
41import org.openstreetmap.josm.actions.search.SearchAction;
42import org.openstreetmap.josm.data.osm.DataSelectionListener;
43import org.openstreetmap.josm.data.osm.OsmPrimitive;
44import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
45import org.openstreetmap.josm.data.osm.search.SearchMode;
46import org.openstreetmap.josm.data.preferences.NamedColorProperty;
47import org.openstreetmap.josm.gui.ExtendedDialog;
48import org.openstreetmap.josm.gui.MainApplication;
49import org.openstreetmap.josm.gui.layer.OsmDataLayer;
50import org.openstreetmap.josm.gui.tagging.ac.AutoCompletingTextField;
51import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionList;
52import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionManager;
53import org.openstreetmap.josm.gui.util.HighlightHelper;
54import org.openstreetmap.josm.gui.util.TableHelper;
55import org.openstreetmap.josm.gui.util.WindowGeometry;
56import org.openstreetmap.josm.gui.widgets.HistoryComboBox;
57import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher;
58import org.openstreetmap.josm.spi.preferences.Config;
59import org.openstreetmap.josm.tools.GBC;
60import org.openstreetmap.josm.tools.ImageProvider;
61import org.openstreetmap.josm.tools.Logging;
62
63/**
64 * Dialog for editing multiple object tags
65 */
66public class MultiTagDialog extends ExtendedDialog implements DataSelectionListener {
67
68 private final MultiTaggerTableModel tableModel = new MultiTaggerTableModel();
69 private final JTable tbl;
70
71 private final HighlightHelper highlightHelper = new HighlightHelper();
72 private final HistoryComboBox cbTagSet = new HistoryComboBox();
73 private List<OsmPrimitive> currentSelection;
74
75 private static final String HISTORY_KEY = "utilsplugin2.multitaghistory";
76 String[] defaultHistory = {"addr:street, addr:housenumber, building, ${area}",
77 "highway, name, ${id}, ${length}",
78 "name name:en name:ru name:de"};
79
80 public MultiTagDialog() {
81 super(MainApplication.getMainFrame(), tr("Edit tags"), new String[]{tr("Ok"), tr("Cancel")}, false);
82 JPanel pnl = new JPanel(new GridBagLayout());
83 tbl = createTable();
84
85 cbTagSet.addItemListener(tagSetChanger);
86 cbTagSet.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
87 .put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0, true), "applyTagSet");
88 cbTagSet.getActionMap().put("applyTagSet", tagSetChanger);
89
90 tbl.addMouseListener(new PopupMenuLauncher(createPopupMenu()));
91
92 pnl.add(cbTagSet, GBC.std().fill(GBC.HORIZONTAL));
93 pnl.add(new JButton(new DeleteFromHistoryAction()), GBC.std());
94 pnl.add(new JButton(new FindMatchingAction()), GBC.std());
95 final JToggleButton jt = new JToggleButton("", ImageProvider.get("restart"), true);
96 jt.setToolTipText(tr("Sync with JOSM selection"));
97 jt.addActionListener(e -> tableModel.setWatchSelection(jt.isSelected()));
98 pnl.add(jt, GBC.eol());
99
100 pnl.add(createTypeFilterPanel(), GBC.eol().fill(GBC.HORIZONTAL));
101 pnl.add(tbl.getTableHeader(), GBC.eop().fill(GBC.HORIZONTAL));
102
103 pnl.add(new JScrollPane(tbl), GBC.eol().fill(GBC.BOTH));
104 setContent(pnl);
105 setDefaultButton(-1);
106 loadHistory();
107
108 WindowGeometry defaultGeometry = WindowGeometry.centerInWindow(MainApplication.getMainFrame(), new Dimension(500, 500));
109 setRememberWindowGeometry(getClass().getName() + ".geometry", defaultGeometry);
110 }
111
112 private JTable createTable() {
113 JTable t = new JTable(tableModel);
114 tableModel.setTable(t);
115 t.setFillsViewportHeight(true);
116 t.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
117 t.addMouseListener(tableMouseAdapter);
118 t.setRowSelectionAllowed(true);
119 t.setColumnSelectionAllowed(true);
120 t.setDefaultRenderer(OsmPrimitiveType.class, new PrimitiveTypeIconRenderer());
121 t.setDefaultRenderer(String.class, new ColoredRenderer());
122 t.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);
123 t.getSelectionModel().addListSelectionListener(selectionListener);
124 return t;
125 }
126
127 private JPanel createTypeFilterPanel() {
128 JPanel p = new JPanel();
129 p.setLayout(new BoxLayout(p, BoxLayout.X_AXIS));
130 for (final OsmPrimitiveType type: OsmPrimitiveType.values()) {
131 final JToggleButton jt = new JToggleButton("", ImageProvider.get(type), true);
132 jt.addActionListener(e -> {
133 if (jt.isSelected())
134 tableModel.shownTypes.add(type);
135 else
136 tableModel.shownTypes.remove(type);
137 tableModel.updateData(MainApplication.getLayerManager().getEditDataSet().getSelected());
138 });
139 p.add(jt);
140 }
141 return p;
142 }
143
144 private void loadHistory() {
145 List<String> cmtHistory = new LinkedList<>(
146 Config.getPref().getList(HISTORY_KEY, Arrays.asList(defaultHistory)));
147 Collections.reverse(cmtHistory);
148 cbTagSet.setPossibleItems(cmtHistory);
149 String s = cmtHistory.get(cmtHistory.size()-1);
150 cbTagSet.setText(s);
151 specifyTagSet(s);
152 }
153
154 @Override
155 protected void buttonAction(int buttonIndex, ActionEvent evt) {
156 highlightHelper.clear();
157 tbl.getSelectionModel().removeListSelectionListener(selectionListener);
158 super.buttonAction(buttonIndex, evt);
159 }
160
161 @Override
162 public void selectionChanged(SelectionChangeEvent event) {
163 tableModel.selectionChanged(event);
164 }
165
166 void doSelectionChanged(Collection<? extends OsmPrimitive> newSelection) {
167 tableModel.doSelectionChanged(newSelection);
168 }
169
170 private final MouseAdapter tableMouseAdapter = new MouseAdapter() {
171 @Override
172 public void mouseClicked(MouseEvent e) {
173 if (e.getClickCount() > 1 && MainApplication.isDisplayingMapView()) {
174 AutoScaleAction.zoomTo(currentSelection);
175 }
176 }
177
178 };
179
180 private final ListSelectionListener selectionListener = e -> {
181 currentSelection = getSelectedPrimitives();
182 if (currentSelection != null && MainApplication.isDisplayingMapView()
183 && highlightHelper.highlightOnly(currentSelection)) {
184 MainApplication.getMap().mapView.repaint();
185 }
186 };
187
188 public List<OsmPrimitive> getSelectedPrimitives() {
189 ArrayList<OsmPrimitive> sel = new ArrayList<>(100);
190 for (int idx: tbl.getSelectedRows()) {
191 sel.add(tableModel.getPrimitiveAt(tbl.convertRowIndexToModel(idx)));
192 }
193 return sel;
194 }
195
196 private final TagSetChanger tagSetChanger = new TagSetChanger();
197
198 private void initAutocompletion() {
199 OsmDataLayer l = MainApplication.getLayerManager().getEditLayer();
200 AutoCompletionManager autocomplete = AutoCompletionManager.of(l.data);
201 for (int i = 0; i < tableModel.mainTags.length; i++) {
202 if (tableModel.isSpecialTag[i]) continue;
203 AutoCompletingTextField tf = new AutoCompletingTextField(0, false);
204 AutoCompletionList acList = new AutoCompletionList();
205 autocomplete.populateWithTagValues(acList, tableModel.mainTags[i]);
206 tf.setAutoCompletionList(acList);
207 tbl.getColumnModel().getColumn(i+1).setCellEditor(tf);
208 }
209 }
210
211 private JPopupMenu createPopupMenu() {
212 JPopupMenu menu = new JPopupMenu();
213 menu.add(new AbstractAction(tr("Zoom to objects"), ImageProvider.get("dialogs/autoscale", "selection")) {
214 @Override
215 public void actionPerformed(ActionEvent e) {
216 if (MainApplication.isDisplayingMapView()) {
217 AutoScaleAction.zoomTo(currentSelection);
218 }
219 }
220 });
221 menu.add(new AbstractAction(tr("Select"), ImageProvider.get("dialogs", "select")) {
222 @Override
223 public void actionPerformed(ActionEvent e) {
224 MainApplication.getLayerManager().getEditDataSet().setSelected(getSelectedPrimitives());
225 }
226 });
227 menu.add(new AbstractAction(tr("Remove tag"), ImageProvider.get("dialogs", "delete")) {
228 @Override
229 public void actionPerformed(ActionEvent e) {
230 tableModel.setAutoCommit(false);
231 for (int c: tbl.getSelectedColumns()) {
232 for (int r: tbl.getSelectedRows()) {
233 tableModel.setValueAt("", tbl.convertRowIndexToModel(r), tbl.convertColumnIndexToModel(c));
234 }
235 }
236 tableModel.commit(tr("Delete tags from multiple objects"));
237 tableModel.setAutoCommit(true);
238 }
239 });
240 menu.add(new AbstractAction(tr("Duplicate tags from the first"), ImageProvider.get("copy")) {
241 @Override
242 public void actionPerformed(ActionEvent e) {
243 tableModel.setAutoCommit(false);
244 for (int c: tbl.getSelectedColumns()) {
245 if (c == 0 || tableModel.isSpecialTag[c-1]) continue;
246 boolean first = true;
247 String value = "";
248 for (int r: tbl.getSelectedRows()) {
249 if (first) {
250 value = (String) tableModel.getValueAt(tbl.convertRowIndexToModel(r), tbl.convertColumnIndexToModel(c));
251 }
252 first = false;
253 tableModel.setValueAt(value, tbl.convertRowIndexToModel(r), tbl.convertColumnIndexToModel(c));
254 }
255 }
256 tableModel.commit(tr("Set tags for multiple objects"));
257 tableModel.setAutoCommit(true);
258 }
259 });
260 return menu;
261 }
262
263 private static class PrimitiveTypeIconRenderer extends DefaultTableCellRenderer {
264 @Override
265 public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
266 Component c = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
267 if (c instanceof JLabel && value instanceof OsmPrimitiveType) {
268 ((JLabel) c).setIcon(ImageProvider.get((OsmPrimitiveType) value));
269 ((JLabel) c).setText("");
270 }
271 return c;
272 }
273 }
274
275 private class DeleteFromHistoryAction extends AbstractAction {
276 DeleteFromHistoryAction() {
277 super("", ImageProvider.get("dialogs", "delete"));
278 putValue(SHORT_DESCRIPTION, tr("Delete from history"));
279 }
280
281 @Override
282 public void actionPerformed(ActionEvent e) {
283 String txt = cbTagSet.getText();
284 Logging.debug(txt);
285 List<String> history = cbTagSet.getHistory();
286 history.remove(txt);
287 if (history.isEmpty()) {
288 history = Arrays.asList(defaultHistory);
289 }
290 Config.getPref().putList(HISTORY_KEY, history);
291 loadHistory();
292 }
293 }
294
295 private class FindMatchingAction extends AbstractAction {
296 FindMatchingAction() {
297 super("", ImageProvider.get("dialogs", "search"));
298 putValue(SHORT_DESCRIPTION, tr("Find primitives with these tags"));
299 }
300
301 @Override
302 public void actionPerformed(ActionEvent e) {
303 SearchAction.search(tableModel.getSearchExpression(), SearchMode.replace);
304 }
305 }
306
307 private class TagSetChanger extends AbstractAction implements ItemListener {
308 String oldTags;
309
310 @Override
311 public void itemStateChanged(ItemEvent e) {
312 // skip text-changing enevts, we need only combobox-selecting ones
313 if (cbTagSet.getSelectedIndex() < 0) return;
314 actionPerformed(null);
315 }
316
317 @Override
318 public void actionPerformed(ActionEvent e) {
319 String s = cbTagSet.getText();
320 if (s == null || s.isEmpty() || s.equals(oldTags)) return;
321 oldTags = s;
322 cbTagSet.addCurrentItemToHistory();
323 Config.getPref().putList(HISTORY_KEY, cbTagSet.getHistory());
324 specifyTagSet(s);
325 }
326 }
327
328 private void specifyTagSet(String s) {
329 Logging.info("Multitagger tags="+s);
330 tableModel.setupColumnsFromText(s);
331
332 tbl.createDefaultColumnsFromModel();
333 tbl.setAutoCreateRowSorter(true);
334
335 tbl.getColumnModel().getColumn(0).setMaxWidth(20);
336 for (int i = 1; i < tableModel.getColumnCount(); i++) {
337 TableHelper.adjustColumnWidth(tbl, i, 100);
338 }
339 initAutocompletion();
340 tableModel.fireTableDataChanged();
341 }
342
343 class ColoredRenderer extends DefaultTableCellRenderer {
344 private final Color highlightColor = new NamedColorProperty(
345 marktr("Multitag Background: highlight"), new Color(255, 255, 200)).get();
346 @Override
347 public Component getTableCellRendererComponent(JTable table, Object value, boolean
348 isSelected, boolean hasFocus, int row, int column) {
349 int row1 = tbl.convertRowIndexToModel(row);
350 JLabel label = (JLabel) super.getTableCellRendererComponent(
351 table, value, isSelected, hasFocus, row, column);
352 if (tbl.isRowSelected(row1) && !tbl.isColumnSelected(column)) {
353 label.setBackground(highlightColor);
354 } else {
355 if (isSelected) {
356 label.setBackground(UIManager.getColor("Table.selectionBackground"));
357 } else {
358 label.setBackground(UIManager.getColor("Table.background"));
359 }
360 }
361 return label;
362 }
363 }
364
365 @Override
366 public void setVisible(boolean visible) {
367 super.setVisible(visible);
368 if (!visible) {
369 tableModel.updateData(Collections.emptyList());
370 }
371 }
372}
Note: See TracBrowser for help on using the repository browser.