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

Last change on this file since 7687 was 7687, checked in by stoecker, 10 years ago

see #10684, see #10688 fix icon scaling a bit

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