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

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

checkstyle - Comments Indentation

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