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

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

javadoc

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