source: josm/trunk/src/org/openstreetmap/josm/gui/preferences/ToolbarPreferences.java@ 10662

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

see #12472, fix #13230, fix #13225, fix #13228 - disable ReferenceEquality warning + partial revert of r10656 + r10659, causes too much problems

  • Property svn:eol-style set to native
File size: 45.3 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.preferences;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.Component;
7import java.awt.Container;
8import java.awt.Dimension;
9import java.awt.GraphicsEnvironment;
10import java.awt.GridBagLayout;
11import java.awt.GridLayout;
12import java.awt.LayoutManager;
13import java.awt.Rectangle;
14import java.awt.datatransfer.DataFlavor;
15import java.awt.datatransfer.Transferable;
16import java.awt.datatransfer.UnsupportedFlavorException;
17import java.awt.event.ActionEvent;
18import java.awt.event.ActionListener;
19import java.awt.event.InputEvent;
20import java.awt.event.KeyEvent;
21import java.io.IOException;
22import java.util.ArrayList;
23import java.util.Arrays;
24import java.util.Collection;
25import java.util.Collections;
26import java.util.LinkedList;
27import java.util.List;
28import java.util.Map;
29import java.util.concurrent.ConcurrentHashMap;
30
31import javax.swing.AbstractAction;
32import javax.swing.Action;
33import javax.swing.DefaultListCellRenderer;
34import javax.swing.DefaultListModel;
35import javax.swing.Icon;
36import javax.swing.ImageIcon;
37import javax.swing.JButton;
38import javax.swing.JCheckBoxMenuItem;
39import javax.swing.JComponent;
40import javax.swing.JLabel;
41import javax.swing.JList;
42import javax.swing.JMenuItem;
43import javax.swing.JPanel;
44import javax.swing.JPopupMenu;
45import javax.swing.JScrollPane;
46import javax.swing.JTable;
47import javax.swing.JToolBar;
48import javax.swing.JTree;
49import javax.swing.ListCellRenderer;
50import javax.swing.MenuElement;
51import javax.swing.TransferHandler;
52import javax.swing.event.PopupMenuEvent;
53import javax.swing.event.PopupMenuListener;
54import javax.swing.table.AbstractTableModel;
55import javax.swing.tree.DefaultMutableTreeNode;
56import javax.swing.tree.DefaultTreeCellRenderer;
57import javax.swing.tree.DefaultTreeModel;
58import javax.swing.tree.TreePath;
59
60import org.openstreetmap.josm.Main;
61import org.openstreetmap.josm.actions.ActionParameter;
62import org.openstreetmap.josm.actions.AdaptableAction;
63import org.openstreetmap.josm.actions.JosmAction;
64import org.openstreetmap.josm.actions.ParameterizedAction;
65import org.openstreetmap.josm.actions.ParameterizedActionDecorator;
66import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset;
67import org.openstreetmap.josm.tools.GBC;
68import org.openstreetmap.josm.tools.ImageProvider;
69import org.openstreetmap.josm.tools.Shortcut;
70
71public class ToolbarPreferences implements PreferenceSettingFactory {
72
73 private static final String EMPTY_TOOLBAR_MARKER = "<!-empty-!>";
74
75 public static class ActionDefinition {
76 private final Action action;
77 private String name = "";
78 private String icon = "";
79 private ImageIcon ico;
80 private final Map<String, Object> parameters = new ConcurrentHashMap<>();
81
82 public ActionDefinition(Action action) {
83 this.action = action;
84 }
85
86 public Map<String, Object> getParameters() {
87 return parameters;
88 }
89
90 public Action getParametrizedAction() {
91 if (getAction() instanceof ParameterizedAction)
92 return new ParameterizedActionDecorator((ParameterizedAction) getAction(), parameters);
93 else
94 return getAction();
95 }
96
97 public Action getAction() {
98 return action;
99 }
100
101 public String getName() {
102 return name;
103 }
104
105 public String getDisplayName() {
106 return name.isEmpty() ? (String) action.getValue(Action.NAME) : name;
107 }
108
109 public String getDisplayTooltip() {
110 if (!name.isEmpty())
111 return name;
112
113 Object tt = action.getValue(TaggingPreset.OPTIONAL_TOOLTIP_TEXT);
114 if (tt != null)
115 return (String) tt;
116
117 return (String) action.getValue(Action.SHORT_DESCRIPTION);
118 }
119
120 public Icon getDisplayIcon() {
121 if (ico != null)
122 return ico;
123 Object o = action.getValue(Action.LARGE_ICON_KEY);
124 if (o == null)
125 o = action.getValue(Action.SMALL_ICON);
126 return (Icon) o;
127 }
128
129 public void setName(String name) {
130 this.name = name;
131 }
132
133 public String getIcon() {
134 return icon;
135 }
136
137 public void setIcon(String icon) {
138 this.icon = icon;
139 ico = ImageProvider.getIfAvailable("", icon);
140 }
141
142 public boolean isSeparator() {
143 return action == null;
144 }
145
146 public static ActionDefinition getSeparator() {
147 return new ActionDefinition(null);
148 }
149
150 public boolean hasParameters() {
151 if (!(getAction() instanceof ParameterizedAction)) return false;
152 for (Object o: parameters.values()) {
153 if (o != null) return true;
154 }
155 return false;
156 }
157 }
158
159 public static class ActionParser {
160 private final Map<String, Action> actions;
161 private final StringBuilder result = new StringBuilder();
162 private int index;
163 private char[] s;
164
165 public ActionParser(Map<String, Action> actions) {
166 this.actions = actions;
167 }
168
169 private String readTillChar(char ch1, char ch2) {
170 result.setLength(0);
171 while (index < s.length && s[index] != ch1 && s[index] != ch2) {
172 if (s[index] == '\\') {
173 index++;
174 if (index >= s.length) {
175 break;
176 }
177 }
178 result.append(s[index]);
179 index++;
180 }
181 return result.toString();
182 }
183
184 private void skip(char ch) {
185 if (index < s.length && s[index] == ch) {
186 index++;
187 }
188 }
189
190 public ActionDefinition loadAction(String actionName) {
191 index = 0;
192 this.s = actionName.toCharArray();
193
194 String name = readTillChar('(', '{');
195 Action action = actions.get(name);
196
197 if (action == null)
198 return null;
199
200 ActionDefinition result = new ActionDefinition(action);
201
202 if (action instanceof ParameterizedAction) {
203 skip('(');
204
205 ParameterizedAction parametrizedAction = (ParameterizedAction) action;
206 Map<String, ActionParameter<?>> actionParams = new ConcurrentHashMap<>();
207 for (ActionParameter<?> param: parametrizedAction.getActionParameters()) {
208 actionParams.put(param.getName(), param);
209 }
210
211 while (index < s.length && s[index] != ')') {
212 String paramName = readTillChar('=', '=');
213 skip('=');
214 String paramValue = readTillChar(',', ')');
215 if (!paramName.isEmpty() && !paramValue.isEmpty()) {
216 ActionParameter<?> actionParam = actionParams.get(paramName);
217 if (actionParam != null) {
218 result.getParameters().put(paramName, actionParam.readFromString(paramValue));
219 }
220 }
221 skip(',');
222 }
223 skip(')');
224 }
225 if (action instanceof AdaptableAction) {
226 skip('{');
227
228 while (index < s.length && s[index] != '}') {
229 String paramName = readTillChar('=', '=');
230 skip('=');
231 String paramValue = readTillChar(',', '}');
232 if ("icon".equals(paramName) && !paramValue.isEmpty()) {
233 result.setIcon(paramValue);
234 } else if ("name".equals(paramName) && !paramValue.isEmpty()) {
235 result.setName(paramValue);
236 }
237 skip(',');
238 }
239 skip('}');
240 }
241
242 return result;
243 }
244
245 private void escape(String s) {
246 for (int i = 0; i < s.length(); i++) {
247 char ch = s.charAt(i);
248 if (ch == '\\' || ch == '(' || ch == '{' || ch == ',' || ch == ')' || ch == '}' || ch == '=') {
249 result.append('\\');
250 result.append(ch);
251 } else {
252 result.append(ch);
253 }
254 }
255 }
256
257 @SuppressWarnings("unchecked")
258 public String saveAction(ActionDefinition action) {
259 result.setLength(0);
260
261 String val = (String) action.getAction().getValue("toolbar");
262 if (val == null)
263 return null;
264 escape(val);
265 if (action.getAction() instanceof ParameterizedAction) {
266 result.append('(');
267 List<ActionParameter<?>> params = ((ParameterizedAction) action.getAction()).getActionParameters();
268 for (int i = 0; i < params.size(); i++) {
269 ActionParameter<Object> param = (ActionParameter<Object>) params.get(i);
270 escape(param.getName());
271 result.append('=');
272 Object value = action.getParameters().get(param.getName());
273 if (value != null) {
274 escape(param.writeToString(value));
275 }
276 if (i < params.size() - 1) {
277 result.append(',');
278 } else {
279 result.append(')');
280 }
281 }
282 }
283 if (action.getAction() instanceof AdaptableAction) {
284 boolean first = true;
285 String tmp = action.getName();
286 if (!tmp.isEmpty()) {
287 result.append(first ? "{" : ",");
288 result.append("name=");
289 escape(tmp);
290 first = false;
291 }
292 tmp = action.getIcon();
293 if (!tmp.isEmpty()) {
294 result.append(first ? "{" : ",");
295 result.append("icon=");
296 escape(tmp);
297 first = false;
298 }
299 if (!first) {
300 result.append('}');
301 }
302 }
303
304 return result.toString();
305 }
306 }
307
308 private static class ActionParametersTableModel extends AbstractTableModel {
309
310 private transient ActionDefinition currentAction = ActionDefinition.getSeparator();
311
312 @Override
313 public int getColumnCount() {
314 return 2;
315 }
316
317 @Override
318 public int getRowCount() {
319 int adaptable = (currentAction.getAction() instanceof AdaptableAction) ? 2 : 0;
320 if (currentAction.isSeparator() || !(currentAction.getAction() instanceof ParameterizedAction))
321 return adaptable;
322 ParameterizedAction pa = (ParameterizedAction) currentAction.getAction();
323 return pa.getActionParameters().size() + adaptable;
324 }
325
326 @SuppressWarnings("unchecked")
327 private ActionParameter<Object> getParam(int index) {
328 ParameterizedAction pa = (ParameterizedAction) currentAction.getAction();
329 return (ActionParameter<Object>) pa.getActionParameters().get(index);
330 }
331
332 @Override
333 public Object getValueAt(int rowIndex, int columnIndex) {
334 if (currentAction.getAction() instanceof AdaptableAction) {
335 if (rowIndex < 2) {
336 switch (columnIndex) {
337 case 0:
338 return rowIndex == 0 ? tr("Tooltip") : tr("Icon");
339 case 1:
340 return rowIndex == 0 ? currentAction.getName() : currentAction.getIcon();
341 default:
342 return null;
343 }
344 } else {
345 rowIndex -= 2;
346 }
347 }
348 ActionParameter<Object> param = getParam(rowIndex);
349 switch (columnIndex) {
350 case 0:
351 return param.getName();
352 case 1:
353 return param.writeToString(currentAction.getParameters().get(param.getName()));
354 default:
355 return null;
356 }
357 }
358
359 @Override
360 public boolean isCellEditable(int row, int column) {
361 return column == 1;
362 }
363
364 @Override
365 public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
366 String val = (String) aValue;
367 int paramIndex = rowIndex;
368
369 if (currentAction.getAction() instanceof AdaptableAction) {
370 if (rowIndex == 0) {
371 currentAction.setName(val);
372 return;
373 } else if (rowIndex == 1) {
374 currentAction.setIcon(val);
375 return;
376 } else {
377 paramIndex -= 2;
378 }
379 }
380 ActionParameter<Object> param = getParam(paramIndex);
381
382 if (param != null && !val.isEmpty()) {
383 currentAction.getParameters().put(param.getName(), param.readFromString((String) aValue));
384 }
385 }
386
387 public void setCurrentAction(ActionDefinition currentAction) {
388 this.currentAction = currentAction;
389 fireTableDataChanged();
390 }
391 }
392
393 private class ToolbarPopupMenu extends JPopupMenu {
394 private transient ActionDefinition act;
395
396 private void setActionAndAdapt(ActionDefinition action) {
397 this.act = action;
398 doNotHide.setSelected(Main.pref.getBoolean("toolbar.always-visible", true));
399 remove.setVisible(act != null);
400 shortcutEdit.setVisible(act != null);
401 }
402
403 private final JMenuItem remove = new JMenuItem(new AbstractAction(tr("Remove from toolbar")) {
404 @Override
405 public void actionPerformed(ActionEvent e) {
406 Collection<String> t = new LinkedList<>(getToolString());
407 ActionParser parser = new ActionParser(null);
408 // get text definition of current action
409 String res = parser.saveAction(act);
410 // remove the button from toolbar preferences
411 t.remove(res);
412 Main.pref.putCollection("toolbar", t);
413 Main.toolbar.refreshToolbarControl();
414 }
415 });
416
417 private final JMenuItem configure = new JMenuItem(new AbstractAction(tr("Configure toolbar")) {
418 @Override
419 public void actionPerformed(ActionEvent e) {
420 final PreferenceDialog p = new PreferenceDialog(Main.parent);
421 p.selectPreferencesTabByName("toolbar");
422 p.setVisible(true);
423 }
424 });
425
426 private final JMenuItem shortcutEdit = new JMenuItem(new AbstractAction(tr("Edit shortcut")) {
427 @Override
428 public void actionPerformed(ActionEvent e) {
429 final PreferenceDialog p = new PreferenceDialog(Main.parent);
430 p.getTabbedPane().getShortcutPreference().setDefaultFilter(act.getDisplayName());
431 p.selectPreferencesTabByName("shortcuts");
432 p.setVisible(true);
433 // refresh toolbar to try using changed shortcuts without restart
434 Main.toolbar.refreshToolbarControl();
435 }
436 });
437
438 private final JCheckBoxMenuItem doNotHide = new JCheckBoxMenuItem(new AbstractAction(tr("Do not hide toolbar and menu")) {
439 @Override
440 public void actionPerformed(ActionEvent e) {
441 boolean sel = ((JCheckBoxMenuItem) e.getSource()).getState();
442 Main.pref.put("toolbar.always-visible", sel);
443 Main.pref.put("menu.always-visible", sel);
444 }
445 });
446
447 {
448 addPopupMenuListener(new PopupMenuListener() {
449 @Override
450 public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
451 setActionAndAdapt(buttonActions.get(
452 ((JPopupMenu) e.getSource()).getInvoker()
453 ));
454 }
455
456 @Override
457 public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
458 // Do nothing
459 }
460
461 @Override
462 public void popupMenuCanceled(PopupMenuEvent e) {
463 // Do nothing
464 }
465 });
466 add(remove);
467 add(configure);
468 add(shortcutEdit);
469 add(doNotHide);
470 }
471 }
472
473 private final ToolbarPopupMenu popupMenu = new ToolbarPopupMenu();
474
475 /**
476 * Key: Registered name (property "toolbar" of action).
477 * Value: The action to execute.
478 */
479 private final Map<String, Action> actions = new ConcurrentHashMap<>();
480 private final Map<String, Action> regactions = new ConcurrentHashMap<>();
481
482 private final DefaultMutableTreeNode rootActionsNode = new DefaultMutableTreeNode(tr("Actions"));
483
484 public final JToolBar control = new JToolBar();
485 private final Map<Object, ActionDefinition> buttonActions = new ConcurrentHashMap<>(30);
486
487 @Override
488 public PreferenceSetting createPreferenceSetting() {
489 return new Settings(rootActionsNode);
490 }
491
492 public class Settings extends DefaultTabPreferenceSetting {
493
494 private final class SelectedListTransferHandler extends TransferHandler {
495 @Override
496 @SuppressWarnings("unchecked")
497 protected Transferable createTransferable(JComponent c) {
498 List<ActionDefinition> actions = new ArrayList<>();
499 for (ActionDefinition o: ((JList<ActionDefinition>) c).getSelectedValuesList()) {
500 actions.add(o);
501 }
502 return new ActionTransferable(actions);
503 }
504
505 @Override
506 public int getSourceActions(JComponent c) {
507 return TransferHandler.MOVE;
508 }
509
510 @Override
511 public boolean canImport(JComponent comp, DataFlavor[] transferFlavors) {
512 for (DataFlavor f : transferFlavors) {
513 if (ACTION_FLAVOR.equals(f))
514 return true;
515 }
516 return false;
517 }
518
519 @Override
520 public void exportAsDrag(JComponent comp, InputEvent e, int action) {
521 super.exportAsDrag(comp, e, action);
522 movingComponent = "list";
523 }
524
525 @Override
526 public boolean importData(JComponent comp, Transferable t) {
527 try {
528 int dropIndex = selectedList.locationToIndex(selectedList.getMousePosition(true));
529 @SuppressWarnings("unchecked")
530 List<ActionDefinition> draggedData = (List<ActionDefinition>) t.getTransferData(ACTION_FLAVOR);
531
532 Object leadItem = dropIndex >= 0 ? selected.elementAt(dropIndex) : null;
533 int dataLength = draggedData.size();
534
535 if (leadItem != null) {
536 for (Object o: draggedData) {
537 if (leadItem.equals(o))
538 return false;
539 }
540 }
541
542 int dragLeadIndex = -1;
543 boolean localDrop = "list".equals(movingComponent);
544
545 if (localDrop) {
546 dragLeadIndex = selected.indexOf(draggedData.get(0));
547 for (Object o: draggedData) {
548 selected.removeElement(o);
549 }
550 }
551 int[] indices = new int[dataLength];
552
553 if (localDrop) {
554 int adjustedLeadIndex = selected.indexOf(leadItem);
555 int insertionAdjustment = dragLeadIndex <= adjustedLeadIndex ? 1 : 0;
556 for (int i = 0; i < dataLength; i++) {
557 selected.insertElementAt(draggedData.get(i), adjustedLeadIndex + insertionAdjustment + i);
558 indices[i] = adjustedLeadIndex + insertionAdjustment + i;
559 }
560 } else {
561 for (int i = 0; i < dataLength; i++) {
562 selected.add(dropIndex, draggedData.get(i));
563 indices[i] = dropIndex + i;
564 }
565 }
566 selectedList.clearSelection();
567 selectedList.setSelectedIndices(indices);
568 movingComponent = "";
569 return true;
570 } catch (IOException | UnsupportedFlavorException e) {
571 Main.error(e);
572 }
573 return false;
574 }
575
576 @Override
577 protected void exportDone(JComponent source, Transferable data, int action) {
578 if ("list".equals(movingComponent)) {
579 try {
580 List<?> draggedData = (List<?>) data.getTransferData(ACTION_FLAVOR);
581 boolean localDrop = selected.contains(draggedData.get(0));
582 if (localDrop) {
583 int[] indices = selectedList.getSelectedIndices();
584 Arrays.sort(indices);
585 for (int i = indices.length - 1; i >= 0; i--) {
586 selected.remove(indices[i]);
587 }
588 }
589 } catch (IOException | UnsupportedFlavorException e) {
590 Main.error(e);
591 }
592 movingComponent = "";
593 }
594 }
595 }
596
597 private final class Move implements ActionListener {
598 @Override
599 public void actionPerformed(ActionEvent e) {
600 if ("<".equals(e.getActionCommand()) && actionsTree.getSelectionCount() > 0) {
601
602 int leadItem = selected.getSize();
603 if (selectedList.getSelectedIndex() != -1) {
604 int[] indices = selectedList.getSelectedIndices();
605 leadItem = indices[indices.length - 1];
606 }
607 for (TreePath selectedAction : actionsTree.getSelectionPaths()) {
608 DefaultMutableTreeNode node = (DefaultMutableTreeNode) selectedAction.getLastPathComponent();
609 if (node.getUserObject() == null) {
610 selected.add(leadItem++, ActionDefinition.getSeparator());
611 } else if (node.getUserObject() instanceof Action) {
612 selected.add(leadItem++, new ActionDefinition((Action) node.getUserObject()));
613 }
614 }
615 } else if (">".equals(e.getActionCommand()) && selectedList.getSelectedIndex() != -1) {
616 while (selectedList.getSelectedIndex() != -1) {
617 selected.remove(selectedList.getSelectedIndex());
618 }
619 } else if ("up".equals(e.getActionCommand())) {
620 int i = selectedList.getSelectedIndex();
621 ActionDefinition o = selected.get(i);
622 if (i != 0) {
623 selected.remove(i);
624 selected.add(i-1, o);
625 selectedList.setSelectedIndex(i-1);
626 }
627 } else if ("down".equals(e.getActionCommand())) {
628 int i = selectedList.getSelectedIndex();
629 ActionDefinition o = selected.get(i);
630 if (i != selected.size()-1) {
631 selected.remove(i);
632 selected.add(i+1, o);
633 selectedList.setSelectedIndex(i+1);
634 }
635 }
636 }
637 }
638
639 private class ActionTransferable implements Transferable {
640
641 private final DataFlavor[] flavors = new DataFlavor[] {ACTION_FLAVOR};
642
643 private final List<ActionDefinition> actions;
644
645 ActionTransferable(List<ActionDefinition> actions) {
646 this.actions = actions;
647 }
648
649 @Override
650 public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
651 return actions;
652 }
653
654 @Override
655 public DataFlavor[] getTransferDataFlavors() {
656 return flavors;
657 }
658
659 @Override
660 public boolean isDataFlavorSupported(DataFlavor flavor) {
661 return flavors[0] == flavor;
662 }
663 }
664
665 private final Move moveAction = new Move();
666
667 private final DefaultListModel<ActionDefinition> selected = new DefaultListModel<>();
668 private final JList<ActionDefinition> selectedList = new JList<>(selected);
669
670 private final DefaultTreeModel actionsTreeModel;
671 private final JTree actionsTree;
672
673 private final ActionParametersTableModel actionParametersModel = new ActionParametersTableModel();
674 private final JTable actionParametersTable = new JTable(actionParametersModel);
675 private JPanel actionParametersPanel;
676
677 private final JButton upButton = createButton("up");
678 private final JButton downButton = createButton("down");
679 private final JButton removeButton = createButton(">");
680 private final JButton addButton = createButton("<");
681
682 private String movingComponent;
683
684 /**
685 * Constructs a new {@code Settings}.
686 * @param rootActionsNode root actions node
687 */
688 public Settings(DefaultMutableTreeNode rootActionsNode) {
689 super(/* ICON(preferences/) */ "toolbar", tr("Toolbar customization"), tr("Customize the elements on the toolbar."));
690 actionsTreeModel = new DefaultTreeModel(rootActionsNode);
691 actionsTree = new JTree(actionsTreeModel);
692 }
693
694 private JButton createButton(String name) {
695 JButton b = new JButton();
696 if ("up".equals(name)) {
697 b.setIcon(ImageProvider.get("dialogs", "up"));
698 } else if ("down".equals(name)) {
699 b.setIcon(ImageProvider.get("dialogs", "down"));
700 } else {
701 b.setText(name);
702 }
703 b.addActionListener(moveAction);
704 b.setActionCommand(name);
705 return b;
706 }
707
708 private void updateEnabledState() {
709 int index = selectedList.getSelectedIndex();
710 upButton.setEnabled(index > 0);
711 downButton.setEnabled(index != -1 && index < selectedList.getModel().getSize() - 1);
712 removeButton.setEnabled(index != -1);
713 addButton.setEnabled(actionsTree.getSelectionCount() > 0);
714 }
715
716 @Override
717 public void addGui(PreferenceTabbedPane gui) {
718 actionsTree.setCellRenderer(new DefaultTreeCellRenderer() {
719 @Override
720 public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded,
721 boolean leaf, int row, boolean hasFocus) {
722 DefaultMutableTreeNode node = (DefaultMutableTreeNode) value;
723 JLabel comp = (JLabel) super.getTreeCellRendererComponent(
724 tree, value, sel, expanded, leaf, row, hasFocus);
725 if (node.getUserObject() == null) {
726 comp.setText(tr("Separator"));
727 comp.setIcon(ImageProvider.get("preferences/separator"));
728 } else if (node.getUserObject() instanceof Action) {
729 Action action = (Action) node.getUserObject();
730 comp.setText((String) action.getValue(Action.NAME));
731 comp.setIcon((Icon) action.getValue(Action.SMALL_ICON));
732 }
733 return comp;
734 }
735 });
736
737 ListCellRenderer<ActionDefinition> renderer = new ListCellRenderer<ActionDefinition>() {
738 private final DefaultListCellRenderer def = new DefaultListCellRenderer();
739 @Override
740 public Component getListCellRendererComponent(JList<? extends ActionDefinition> list,
741 ActionDefinition action, int index, boolean isSelected, boolean cellHasFocus) {
742 String s;
743 Icon i;
744 if (!action.isSeparator()) {
745 s = action.getDisplayName();
746 i = action.getDisplayIcon();
747 } else {
748 i = ImageProvider.get("preferences/separator");
749 s = tr("Separator");
750 }
751 JLabel l = (JLabel) def.getListCellRendererComponent(list, s, index, isSelected, cellHasFocus);
752 l.setIcon(i);
753 return l;
754 }
755 };
756 selectedList.setCellRenderer(renderer);
757 selectedList.addListSelectionListener(e -> {
758 boolean sel = selectedList.getSelectedIndex() != -1;
759 if (sel) {
760 actionsTree.clearSelection();
761 ActionDefinition action = selected.get(selectedList.getSelectedIndex());
762 actionParametersModel.setCurrentAction(action);
763 actionParametersPanel.setVisible(actionParametersModel.getRowCount() > 0);
764 }
765 updateEnabledState();
766 });
767
768 if (!GraphicsEnvironment.isHeadless()) {
769 selectedList.setDragEnabled(true);
770 }
771 selectedList.setTransferHandler(new SelectedListTransferHandler());
772
773 actionsTree.setTransferHandler(new TransferHandler() {
774 private static final long serialVersionUID = 1L;
775
776 @Override
777 public int getSourceActions(JComponent c) {
778 return TransferHandler.MOVE;
779 }
780
781 @Override
782 protected Transferable createTransferable(JComponent c) {
783 TreePath[] paths = actionsTree.getSelectionPaths();
784 List<ActionDefinition> dragActions = new ArrayList<>();
785 for (TreePath path : paths) {
786 DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getLastPathComponent();
787 Object obj = node.getUserObject();
788 if (obj == null) {
789 dragActions.add(ActionDefinition.getSeparator());
790 } else if (obj instanceof Action) {
791 dragActions.add(new ActionDefinition((Action) obj));
792 }
793 }
794 return new ActionTransferable(dragActions);
795 }
796 });
797 if (!GraphicsEnvironment.isHeadless()) {
798 actionsTree.setDragEnabled(true);
799 }
800 actionsTree.getSelectionModel().addTreeSelectionListener(e -> updateEnabledState());
801
802 final JPanel left = new JPanel(new GridBagLayout());
803 left.add(new JLabel(tr("Toolbar")), GBC.eol());
804 left.add(new JScrollPane(selectedList), GBC.std().fill(GBC.BOTH));
805
806 final JPanel right = new JPanel(new GridBagLayout());
807 right.add(new JLabel(tr("Available")), GBC.eol());
808 right.add(new JScrollPane(actionsTree), GBC.eol().fill(GBC.BOTH));
809
810 final JPanel buttons = new JPanel(new GridLayout(6, 1));
811 buttons.add(upButton);
812 buttons.add(addButton);
813 buttons.add(removeButton);
814 buttons.add(downButton);
815 updateEnabledState();
816
817 final JPanel p = new JPanel();
818 p.setLayout(new LayoutManager() {
819 @Override
820 public void addLayoutComponent(String name, Component comp) {
821 // Do nothing
822 }
823
824 @Override
825 public void removeLayoutComponent(Component comp) {
826 // Do nothing
827 }
828
829 @Override
830 public Dimension minimumLayoutSize(Container parent) {
831 Dimension l = left.getMinimumSize();
832 Dimension r = right.getMinimumSize();
833 Dimension b = buttons.getMinimumSize();
834 return new Dimension(l.width+b.width+10+r.width, l.height+b.height+10+r.height);
835 }
836
837 @Override
838 public Dimension preferredLayoutSize(Container parent) {
839 Dimension l = new Dimension(200, 200);
840 Dimension r = new Dimension(200, 200);
841 return new Dimension(l.width+r.width+10+buttons.getPreferredSize().width, Math.max(l.height, r.height));
842 }
843
844 @Override
845 public void layoutContainer(Container parent) {
846 Dimension d = p.getSize();
847 Dimension b = buttons.getPreferredSize();
848 int width = (d.width-10-b.width)/2;
849 left.setBounds(new Rectangle(0, 0, width, d.height));
850 right.setBounds(new Rectangle(width+10+b.width, 0, width, d.height));
851 buttons.setBounds(new Rectangle(width+5, d.height/2-b.height/2, b.width, b.height));
852 }
853 });
854 p.add(left);
855 p.add(buttons);
856 p.add(right);
857
858 actionParametersPanel = new JPanel(new GridBagLayout());
859 actionParametersPanel.add(new JLabel(tr("Action parameters")), GBC.eol().insets(0, 10, 0, 20));
860 actionParametersTable.getColumnModel().getColumn(0).setHeaderValue(tr("Parameter name"));
861 actionParametersTable.getColumnModel().getColumn(1).setHeaderValue(tr("Parameter value"));
862 actionParametersPanel.add(actionParametersTable.getTableHeader(), GBC.eol().fill(GBC.HORIZONTAL));
863 actionParametersPanel.add(actionParametersTable, GBC.eol().fill(GBC.BOTH).insets(0, 0, 0, 10));
864 actionParametersPanel.setVisible(false);
865
866 JPanel panel = gui.createPreferenceTab(this);
867 panel.add(p, GBC.eol().fill(GBC.BOTH));
868 panel.add(actionParametersPanel, GBC.eol().fill(GBC.HORIZONTAL));
869 selected.removeAllElements();
870 for (ActionDefinition actionDefinition: getDefinedActions()) {
871 selected.addElement(actionDefinition);
872 }
873 }
874
875 @Override
876 public boolean ok() {
877 Collection<String> t = new LinkedList<>();
878 ActionParser parser = new ActionParser(null);
879 for (int i = 0; i < selected.size(); ++i) {
880 ActionDefinition action = selected.get(i);
881 if (action.isSeparator()) {
882 t.add("|");
883 } else {
884 String res = parser.saveAction(action);
885 if (res != null) {
886 t.add(res);
887 }
888 }
889 }
890 if (t.isEmpty()) {
891 t = Collections.singletonList(EMPTY_TOOLBAR_MARKER);
892 }
893 Main.pref.putCollection("toolbar", t);
894 Main.toolbar.refreshToolbarControl();
895 return false;
896 }
897
898 }
899
900 /**
901 * Constructs a new {@code ToolbarPreferences}.
902 */
903 public ToolbarPreferences() {
904 control.setFloatable(false);
905 control.setComponentPopupMenu(popupMenu);
906 Main.pref.addPreferenceChangeListener(e -> {
907 if ("toolbar.visible".equals(e.getKey())) {
908 refreshToolbarControl();
909 }
910 });
911 }
912
913 private void loadAction(DefaultMutableTreeNode node, MenuElement menu) {
914 Object userObject = null;
915 MenuElement menuElement = menu;
916 if (menu.getSubElements().length > 0 &&
917 menu.getSubElements()[0] instanceof JPopupMenu) {
918 menuElement = menu.getSubElements()[0];
919 }
920 for (MenuElement item : menuElement.getSubElements()) {
921 if (item instanceof JMenuItem) {
922 JMenuItem menuItem = (JMenuItem) item;
923 if (menuItem.getAction() != null) {
924 Action action = menuItem.getAction();
925 userObject = action;
926 Object tb = action.getValue("toolbar");
927 if (tb == null) {
928 Main.info(tr("Toolbar action without name: {0}",
929 action.getClass().getName()));
930 continue;
931 } else if (!(tb instanceof String)) {
932 if (!(tb instanceof Boolean) || (Boolean) tb) {
933 Main.info(tr("Strange toolbar value: {0}",
934 action.getClass().getName()));
935 }
936 continue;
937 } else {
938 String toolbar = (String) tb;
939 Action r = actions.get(toolbar);
940 if (r != null && r != action && !toolbar.startsWith("imagery_")) {
941 Main.info(tr("Toolbar action {0} overwritten: {1} gets {2}",
942 toolbar, r.getClass().getName(), action.getClass().getName()));
943 }
944 actions.put(toolbar, action);
945 }
946 } else {
947 userObject = menuItem.getText();
948 }
949 }
950 DefaultMutableTreeNode newNode = new DefaultMutableTreeNode(userObject);
951 node.add(newNode);
952 loadAction(newNode, item);
953 }
954 }
955
956 private void loadActions() {
957 rootActionsNode.removeAllChildren();
958 loadAction(rootActionsNode, Main.main.menu);
959 for (Map.Entry<String, Action> a : regactions.entrySet()) {
960 if (actions.get(a.getKey()) == null) {
961 rootActionsNode.add(new DefaultMutableTreeNode(a.getValue()));
962 }
963 }
964 rootActionsNode.add(new DefaultMutableTreeNode(null));
965 }
966
967 private static final String[] deftoolbar = {"open", "save", "download", "upload", "|",
968 "undo", "redo", "|", "dialogs/search", "preference", "|", "splitway", "combineway",
969 "wayflip", "|", "imagery-offset", "|", "tagginggroup_Highways/Streets",
970 "tagginggroup_Highways/Ways", "tagginggroup_Highways/Waypoints",
971 "tagginggroup_Highways/Barriers", "|", "tagginggroup_Transport/Car",
972 "tagginggroup_Transport/Public Transport", "|", "tagginggroup_Facilities/Tourism",
973 "tagginggroup_Facilities/Food+Drinks", "|", "tagginggroup_Man Made/Historic Places", "|",
974 "tagginggroup_Man Made/Man Made"};
975
976 public static Collection<String> getToolString() {
977
978 Collection<String> toolStr = Main.pref.getCollection("toolbar", Arrays.asList(deftoolbar));
979 if (toolStr == null || toolStr.isEmpty()) {
980 toolStr = Arrays.asList(deftoolbar);
981 }
982 return toolStr;
983 }
984
985 private Collection<ActionDefinition> getDefinedActions() {
986 loadActions();
987
988 Map<String, Action> allActions = new ConcurrentHashMap<>(regactions);
989 allActions.putAll(actions);
990 ActionParser actionParser = new ActionParser(allActions);
991
992 Collection<ActionDefinition> result = new ArrayList<>();
993
994 for (String s : getToolString()) {
995 if ("|".equals(s)) {
996 result.add(ActionDefinition.getSeparator());
997 } else {
998 ActionDefinition a = actionParser.loadAction(s);
999 if (a != null) {
1000 result.add(a);
1001 } else {
1002 Main.info("Could not load tool definition "+s);
1003 }
1004 }
1005 }
1006
1007 return result;
1008 }
1009
1010 /**
1011 * @param action Action to register
1012 * @return The parameter (for better chaining)
1013 */
1014 public Action register(Action action) {
1015 String toolbar = (String) action.getValue("toolbar");
1016 if (toolbar == null) {
1017 Main.info(tr("Registered toolbar action without name: {0}",
1018 action.getClass().getName()));
1019 } else {
1020 Action r = regactions.get(toolbar);
1021 if (r != null) {
1022 Main.info(tr("Registered toolbar action {0} overwritten: {1} gets {2}",
1023 toolbar, r.getClass().getName(), action.getClass().getName()));
1024 }
1025 }
1026 if (toolbar != null) {
1027 regactions.put(toolbar, action);
1028 }
1029 return action;
1030 }
1031
1032 /**
1033 * Parse the toolbar preference setting and construct the toolbar GUI control.
1034 *
1035 * Call this, if anything has changed in the toolbar settings and you want to refresh
1036 * the toolbar content (e.g. after registering actions in a plugin)
1037 */
1038 public void refreshToolbarControl() {
1039 control.removeAll();
1040 buttonActions.clear();
1041 boolean unregisterTab = Shortcut.findShortcut(KeyEvent.VK_TAB, 0) != null;
1042
1043 for (ActionDefinition action : getDefinedActions()) {
1044 if (action.isSeparator()) {
1045 control.addSeparator();
1046 } else {
1047 final JButton b = addButtonAndShortcut(action);
1048 buttonActions.put(b, action);
1049
1050 Icon i = action.getDisplayIcon();
1051 if (i != null) {
1052 b.setIcon(i);
1053 Dimension s = b.getPreferredSize();
1054 /* make squared toolbar icons */
1055 if (s.width < s.height) {
1056 s.width = s.height;
1057 b.setMinimumSize(s);
1058 b.setMaximumSize(s);
1059 //b.setSize(s);
1060 } else if (s.height < s.width) {
1061 s.height = s.width;
1062 b.setMinimumSize(s);
1063 b.setMaximumSize(s);
1064 }
1065 } else {
1066 // hide action text if an icon is set later (necessary for delayed/background image loading)
1067 action.getParametrizedAction().addPropertyChangeListener(evt -> {
1068 if (Action.SMALL_ICON.equals(evt.getPropertyName())) {
1069 b.setHideActionText(evt.getNewValue() != null);
1070 }
1071 });
1072 }
1073 b.setInheritsPopupMenu(true);
1074 b.setFocusTraversalKeysEnabled(!unregisterTab);
1075 }
1076 }
1077
1078 boolean visible = Main.pref.getBoolean("toolbar.visible", true);
1079
1080 control.setFocusTraversalKeysEnabled(!unregisterTab);
1081 control.setVisible(visible && control.getComponentCount() != 0);
1082 control.repaint();
1083 }
1084
1085 /**
1086 * The method to add custom button on toolbar like search or preset buttons
1087 * @param definitionText toolbar definition text to describe the new button,
1088 * must be carefully generated by using {@link ActionParser}
1089 * @param preferredIndex place to put the new button, give -1 for the end of toolbar
1090 * @param removeIfExists if true and the button already exists, remove it
1091 */
1092 public void addCustomButton(String definitionText, int preferredIndex, boolean removeIfExists) {
1093 List<String> t = new LinkedList<>(getToolString());
1094 if (t.contains(definitionText)) {
1095 if (!removeIfExists) return; // do nothing
1096 t.remove(definitionText);
1097 } else {
1098 if (preferredIndex >= 0 && preferredIndex < t.size()) {
1099 t.add(preferredIndex, definitionText); // add to specified place
1100 } else {
1101 t.add(definitionText); // add to the end
1102 }
1103 }
1104 Main.pref.putCollection("toolbar", t);
1105 Main.toolbar.refreshToolbarControl();
1106 }
1107
1108 private JButton addButtonAndShortcut(ActionDefinition action) {
1109 Action act = action.getParametrizedAction();
1110 JButton b = control.add(act);
1111
1112 Shortcut sc = null;
1113 if (action.getAction() instanceof JosmAction) {
1114 sc = ((JosmAction) action.getAction()).getShortcut();
1115 if (sc.getAssignedKey() == KeyEvent.CHAR_UNDEFINED) {
1116 sc = null;
1117 }
1118 }
1119
1120 long paramCode = 0;
1121 if (action.hasParameters()) {
1122 paramCode = action.parameters.hashCode();
1123 }
1124
1125 String tt = action.getDisplayTooltip();
1126 if (tt == null) {
1127 tt = "";
1128 }
1129
1130 if (sc == null || paramCode != 0) {
1131 String name = (String) action.getAction().getValue("toolbar");
1132 if (name == null) {
1133 name = action.getDisplayName();
1134 }
1135 if (paramCode != 0) {
1136 name = name+paramCode;
1137 }
1138 String desc = action.getDisplayName() + ((paramCode == 0) ? "" : action.parameters.toString());
1139 sc = Shortcut.registerShortcut("toolbar:"+name, tr("Toolbar: {0}", desc),
1140 KeyEvent.CHAR_UNDEFINED, Shortcut.NONE);
1141 Main.unregisterShortcut(sc);
1142 Main.registerActionShortcut(act, sc);
1143
1144 // add shortcut info to the tooltip if needed
1145 if (sc.isAssignedUser()) {
1146 if (tt.startsWith("<html>") && tt.endsWith("</html>")) {
1147 tt = tt.substring(6, tt.length()-6);
1148 }
1149 tt = Main.platform.makeTooltip(tt, sc);
1150 }
1151 }
1152
1153 if (!tt.isEmpty()) {
1154 b.setToolTipText(tt);
1155 }
1156 return b;
1157 }
1158
1159 private static final DataFlavor ACTION_FLAVOR = new DataFlavor(ActionDefinition.class, "ActionItem");
1160}
Note: See TracBrowser for help on using the repository browser.