001    // License: GPL. Copyright 2007 by Immanuel Scholz and others
002    package org.openstreetmap.josm.gui.preferences;
003    
004    import static org.openstreetmap.josm.tools.I18n.tr;
005    
006    import java.awt.Component;
007    import java.awt.Container;
008    import java.awt.Dimension;
009    import java.awt.GridBagLayout;
010    import java.awt.GridLayout;
011    import java.awt.LayoutManager;
012    import java.awt.Rectangle;
013    import java.awt.datatransfer.DataFlavor;
014    import java.awt.datatransfer.Transferable;
015    import java.awt.datatransfer.UnsupportedFlavorException;
016    import java.awt.event.ActionEvent;
017    import java.awt.event.ActionListener;
018    import java.awt.event.InputEvent;
019    import java.beans.PropertyChangeEvent;
020    import java.beans.PropertyChangeListener;
021    import java.io.IOException;
022    import java.util.ArrayList;
023    import java.util.Arrays;
024    import java.util.Collection;
025    import java.util.Collections;
026    import java.util.HashMap;
027    import java.util.LinkedList;
028    import java.util.List;
029    import java.util.Map;
030    
031    import javax.swing.Action;
032    import javax.swing.DefaultListCellRenderer;
033    import javax.swing.DefaultListModel;
034    import javax.swing.Icon;
035    import javax.swing.ImageIcon;
036    import javax.swing.JButton;
037    import javax.swing.JComponent;
038    import javax.swing.JLabel;
039    import javax.swing.JList;
040    import javax.swing.JMenuItem;
041    import javax.swing.JPanel;
042    import javax.swing.JPopupMenu;
043    import javax.swing.JScrollPane;
044    import javax.swing.JTable;
045    import javax.swing.JToolBar;
046    import javax.swing.JTree;
047    import javax.swing.ListCellRenderer;
048    import javax.swing.MenuElement;
049    import javax.swing.TransferHandler;
050    import javax.swing.event.ListSelectionEvent;
051    import javax.swing.event.ListSelectionListener;
052    import javax.swing.event.TreeSelectionEvent;
053    import javax.swing.event.TreeSelectionListener;
054    import javax.swing.table.AbstractTableModel;
055    import javax.swing.tree.DefaultMutableTreeNode;
056    import javax.swing.tree.DefaultTreeCellRenderer;
057    import javax.swing.tree.DefaultTreeModel;
058    import javax.swing.tree.TreePath;
059    
060    import org.openstreetmap.josm.Main;
061    import org.openstreetmap.josm.actions.ActionParameter;
062    import org.openstreetmap.josm.actions.AdaptableAction;
063    import org.openstreetmap.josm.actions.ParameterizedAction;
064    import org.openstreetmap.josm.actions.ParameterizedActionDecorator;
065    import org.openstreetmap.josm.gui.tagging.TaggingPreset;
066    import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher;
067    import org.openstreetmap.josm.tools.GBC;
068    import org.openstreetmap.josm.tools.ImageProvider;
069    
070    public class ToolbarPreferences implements PreferenceSettingFactory {
071    
072    
073        private static final String EMPTY_TOOLBAR_MARKER = "<!-empty-!>";
074    
075        public static class ActionDefinition {
076            private final Action action;
077            private String name = "";
078            private String icon = "";
079            private ImageIcon ico = null;
080            private final Map<String, Object> parameters = new HashMap<String, Object>();
081    
082            public ActionDefinition(Action action) {
083                this.action = action;
084            }
085    
086            public Map<String, Object> getParameters() {
087                return parameters;
088            }
089    
090            public Action getParametrizedAction() {
091                if (getAction() instanceof ParameterizedAction)
092                    return new ParameterizedActionDecorator((ParameterizedAction) getAction(), parameters);
093                else
094                    return getAction();
095            }
096    
097            public Action getAction() {
098                return action;
099            }
100    
101            public String getName() {
102                return name;
103            }
104    
105            public String getDisplayName() {
106                return name.isEmpty() ? (String) action.getValue(Action.NAME) : name;
107            }
108    
109            public String getDisplayTooltip() {
110                if(!name.isEmpty())
111                    return name;
112    
113                Object tt = action.getValue(TaggingPreset.OPTIONAL_TOOLTIP_TEXT);
114                if (tt != null)
115                    return (String) tt;
116    
117                return (String) action.getValue(Action.SHORT_DESCRIPTION);
118            }
119    
120            public Icon getDisplayIcon() {
121                return ico != null ? ico : (Icon) action.getValue(Action.SMALL_ICON);
122            }
123    
124            public void setName(String name) {
125                this.name = name;
126            }
127    
128            public String getIcon() {
129                return icon;
130            }
131    
132            public void setIcon(String icon) {
133                this.icon = icon;
134                ico = ImageProvider.getIfAvailable("", icon);
135            }
136    
137            public boolean isSeparator() {
138                return action == null;
139            }
140    
141            public static ActionDefinition getSeparator() {
142                return new ActionDefinition(null);
143            }
144        }
145    
146        public static class ActionParser {
147            private final Map<String, Action> actions;
148            private final StringBuilder result = new StringBuilder();
149            private int index;
150            private char[] s;
151    
152            public ActionParser(Map<String, Action> actions) {
153                this.actions = actions;
154            }
155    
156            private String readTillChar(char ch1, char ch2) {
157                result.setLength(0);
158                while (index < s.length && s[index] != ch1 && s[index] != ch2) {
159                    if (s[index] == '\\') {
160                        index++;
161                        if (index >= s.length) {
162                            break;
163                        }
164                    }
165                    result.append(s[index]);
166                    index++;
167                }
168                return result.toString();
169            }
170    
171            private void skip(char ch) {
172                if (index < s.length && s[index] == ch) {
173                    index++;
174                }
175            }
176    
177            public ActionDefinition loadAction(String actionName) {
178                index = 0;
179                this.s = actionName.toCharArray();
180    
181                String name = readTillChar('(', '{');
182                Action action = actions.get(name);
183    
184                if (action == null)
185                    return null;
186    
187                ActionDefinition result = new ActionDefinition(action);
188    
189                if (action instanceof ParameterizedAction) {
190                    skip('(');
191    
192                    ParameterizedAction parametrizedAction = (ParameterizedAction)action;
193                    Map<String, ActionParameter<?>> actionParams = new HashMap<String, ActionParameter<?>>();
194                    for (ActionParameter<?> param: parametrizedAction.getActionParameters()) {
195                        actionParams.put(param.getName(), param);
196                    }
197    
198                    while (index < s.length && s[index] != ')') {
199                        String paramName = readTillChar('=', '=');
200                        skip('=');
201                        String paramValue = readTillChar(',',')');
202                        if (paramName.length() > 0) {
203                            ActionParameter<?> actionParam = actionParams.get(paramName);
204                            if (actionParam != null) {
205                                result.getParameters().put(paramName, actionParam.readFromString(paramValue));
206                            }
207                        }
208                        skip(',');
209                    }
210                    skip(')');
211                }
212                if (action instanceof AdaptableAction) {
213                    skip('{');
214    
215                    while (index < s.length && s[index] != '}') {
216                        String paramName = readTillChar('=', '=');
217                        skip('=');
218                        String paramValue = readTillChar(',','}');
219                        if ("icon".equals(paramName) && paramValue.length() > 0)
220                            result.setIcon(paramValue);
221                        else if("name".equals(paramName) && paramValue.length() > 0)
222                            result.setName(paramValue);
223                        skip(',');
224                    }
225                    skip('}');
226                }
227    
228                return result;
229            }
230    
231            private void escape(String s) {
232                for (int i=0; i<s.length(); i++) {
233                    char ch = s.charAt(i);
234                    if (ch == '\\' || ch == '(' || ch == '{' || ch == ',' || ch == ')' || ch == '}' || ch == '=') {
235                        result.append('\\');
236                        result.append(ch);
237                    } else {
238                        result.append(ch);
239                    }
240                }
241            }
242    
243            @SuppressWarnings("unchecked")
244            public String saveAction(ActionDefinition action) {
245                result.setLength(0);
246    
247                String val = (String) action.getAction().getValue("toolbar");
248                if(val == null)
249                    return null;
250                escape(val);
251                if (action.getAction() instanceof ParameterizedAction) {
252                    result.append('(');
253                    List<ActionParameter<?>> params = ((ParameterizedAction)action.getAction()).getActionParameters();
254                    for (int i=0; i<params.size(); i++) {
255                        ActionParameter<Object> param = (ActionParameter<Object>)params.get(i);
256                        escape(param.getName());
257                        result.append('=');
258                        Object value = action.getParameters().get(param.getName());
259                        if (value != null) {
260                            escape(param.writeToString(value));
261                        }
262                        if (i < params.size() - 1) {
263                            result.append(',');
264                        } else {
265                            result.append(')');
266                        }
267                    }
268                }
269                if (action.getAction() instanceof AdaptableAction) {
270                    boolean first = true;
271                    String tmp = action.getName();
272                    if(tmp.length() != 0) {
273                        result.append(first ? "{" : ",");
274                        result.append("name=");
275                        escape(tmp);
276                        first = false;
277                    }
278                    tmp = action.getIcon();
279                    if(tmp.length() != 0) {
280                        result.append(first ? "{" : ",");
281                        result.append("icon=");
282                        escape(tmp);
283                        first = false;
284                    }
285                    if(!first)
286                        result.append('}');
287                }
288    
289                return result.toString();
290            }
291        }
292    
293        private static class ActionParametersTableModel extends AbstractTableModel {
294    
295            private ActionDefinition currentAction = ActionDefinition.getSeparator();
296    
297            public int getColumnCount() {
298                return 2;
299            }
300    
301            public int getRowCount() {
302                int adaptable = ((currentAction.getAction() instanceof AdaptableAction) ? 2 : 0);
303                if (currentAction.isSeparator() || !(currentAction.getAction() instanceof ParameterizedAction))
304                    return adaptable;
305                ParameterizedAction pa = (ParameterizedAction)currentAction.getAction();
306                return pa.getActionParameters().size() + adaptable;
307            }
308    
309            @SuppressWarnings("unchecked")
310            private ActionParameter<Object> getParam(int index) {
311                ParameterizedAction pa = (ParameterizedAction)currentAction.getAction();
312                return (ActionParameter<Object>) pa.getActionParameters().get(index);
313            }
314    
315            public Object getValueAt(int rowIndex, int columnIndex) {
316                if(currentAction.getAction() instanceof AdaptableAction)
317                {
318                    if (rowIndex < 2) {
319                        switch (columnIndex) {
320                        case 0:
321                            return rowIndex == 0 ? tr("Tooltip") : tr("Icon");
322                        case 1:
323                            return rowIndex == 0 ? currentAction.getName() : currentAction.getIcon();
324                        default:
325                            return null;
326                        }
327                    } else
328                        rowIndex -= 2;
329                }
330                ActionParameter<Object> param = getParam(rowIndex);
331                switch (columnIndex) {
332                case 0:
333                    return param.getName();
334                case 1:
335                    return param.writeToString(currentAction.getParameters().get(param.getName()));
336                default:
337                    return null;
338                }
339            }
340    
341            @Override
342            public boolean isCellEditable(int row, int column) {
343                return column == 1;
344            }
345    
346            @Override
347            public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
348                if(currentAction.getAction() instanceof AdaptableAction)
349                {
350                    if (rowIndex == 0) {
351                         currentAction.setName((String)aValue);
352                         return;
353                    } else if (rowIndex == 1) {
354                         currentAction.setIcon((String)aValue);
355                         return;
356                    } else
357                        rowIndex -= 2;
358                }
359                ActionParameter<Object> param = getParam(rowIndex);
360                currentAction.getParameters().put(param.getName(), param.readFromString((String)aValue));
361            }
362    
363    
364            public void setCurrentAction(ActionDefinition currentAction) {
365                this.currentAction = currentAction;
366                fireTableDataChanged();
367            }
368    
369        }
370    
371        private static class ToolbarPopupMenu extends JPopupMenu {
372            public ToolbarPopupMenu(final ActionDefinition action) {
373    
374                if(action != null) {
375                    add(tr("Remove from toolbar",action.getDisplayName()))
376                            .addActionListener(new ActionListener() {
377                                public void actionPerformed(ActionEvent e) {
378                                    Collection<String> t = new LinkedList<String>(getToolString());
379                                    ActionParser parser = new ActionParser(null);
380                                    // get text definition of current action
381                                    String res = parser.saveAction(action);
382                                    // remove the button from toolbar preferences
383                                    t.remove( res );
384                                    Main.pref.putCollection("toolbar", t);
385                                    Main.toolbar.refreshToolbarControl();
386                                }
387                    });
388                }
389                
390                add(tr("Configure toolbar")).addActionListener(new ActionListener() {
391                    public void actionPerformed(ActionEvent e) {
392                        final PreferenceDialog p =new PreferenceDialog(Main.parent);
393                        p.selectPreferencesTabByName("toolbar");
394                        p.setVisible(true);
395                    }
396                });
397                
398            }
399        }
400    
401        /**
402         * Key: Registered name (property "toolbar" of action).
403         * Value: The action to execute.
404         */
405        private Map<String, Action> actions = new HashMap<String, Action>();
406        private Map<String, Action> regactions = new HashMap<String, Action>();
407    
408        private DefaultMutableTreeNode rootActionsNode = new DefaultMutableTreeNode(tr("Actions"));
409    
410        public JToolBar control = new JToolBar();
411    
412        public PreferenceSetting createPreferenceSetting() {
413            return new Settings(rootActionsNode);
414        }
415    
416        public class Settings extends DefaultTabPreferenceSetting {
417    
418            private final class Move implements ActionListener {
419                public void actionPerformed(ActionEvent e) {
420                    if (e.getActionCommand().equals("<") && actionsTree.getSelectionCount() > 0) {
421    
422                        int leadItem = selected.getSize();
423                        if (selectedList.getSelectedIndex() != -1) {
424                            int[] indices = selectedList.getSelectedIndices();
425                            leadItem = indices[indices.length - 1];
426                        }
427                        for (TreePath selectedAction : actionsTree.getSelectionPaths()) {
428                            DefaultMutableTreeNode node = (DefaultMutableTreeNode) selectedAction.getLastPathComponent();
429                            if (node.getUserObject() == null) {
430                                selected.add(leadItem++, ActionDefinition.getSeparator());
431                            } else if (node.getUserObject() instanceof Action) {
432                                selected.add(leadItem++, new ActionDefinition((Action)node.getUserObject()));
433    
434                            }
435                        }
436                    } else if (e.getActionCommand().equals(">") && selectedList.getSelectedIndex() != -1) {
437                        while (selectedList.getSelectedIndex() != -1) {
438                            selected.remove(selectedList.getSelectedIndex());
439                        }
440                    } else if (e.getActionCommand().equals("up")) {
441                        int i = selectedList.getSelectedIndex();
442                        Object o = selected.get(i);
443                        if (i != 0) {
444                            selected.remove(i);
445                            selected.add(i-1, o);
446                            selectedList.setSelectedIndex(i-1);
447                        }
448                    } else if (e.getActionCommand().equals("down")) {
449                        int i = selectedList.getSelectedIndex();
450                        Object o = selected.get(i);
451                        if (i != selected.size()-1) {
452                            selected.remove(i);
453                            selected.add(i+1, o);
454                            selectedList.setSelectedIndex(i+1);
455                        }
456                    }
457                }
458            }
459    
460            private class ActionTransferable implements Transferable {
461    
462                private DataFlavor[] flavors = new DataFlavor[] { ACTION_FLAVOR };
463    
464                private final List<ActionDefinition> actions;
465    
466                public ActionTransferable(List<ActionDefinition> actions) {
467                    this.actions = actions;
468                }
469    
470                public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
471                    return actions;
472                }
473    
474                public DataFlavor[] getTransferDataFlavors() {
475                    return flavors;
476                }
477    
478                public boolean isDataFlavorSupported(DataFlavor flavor) {
479                    return flavors[0] == flavor;
480                }
481            }
482    
483            private final Move moveAction = new Move();
484    
485            private final DefaultListModel selected = new DefaultListModel();
486            private final JList selectedList = new JList(selected);
487    
488            private final DefaultTreeModel actionsTreeModel;
489            private final JTree actionsTree;
490    
491            private final ActionParametersTableModel actionParametersModel = new ActionParametersTableModel();
492            private final JTable actionParametersTable = new JTable(actionParametersModel);
493            private JPanel actionParametersPanel;
494    
495            private JButton upButton;
496            private JButton downButton;
497            private JButton removeButton;
498            private JButton addButton;
499    
500            private String movingComponent;
501    
502            public Settings(DefaultMutableTreeNode rootActionsNode) {
503                super("toolbar", tr("Toolbar customization"), tr("Customize the elements on the toolbar."));
504                actionsTreeModel = new DefaultTreeModel(rootActionsNode);
505                actionsTree = new JTree(actionsTreeModel);
506            }
507    
508            private JButton createButton(String name) {
509                JButton b = new JButton();
510                if (name.equals("up")) {
511                    b.setIcon(ImageProvider.get("dialogs", "up"));
512                } else if (name.equals("down")) {
513                    b.setIcon(ImageProvider.get("dialogs", "down"));
514                } else {
515                    b.setText(name);
516                }
517                b.addActionListener(moveAction);
518                b.setActionCommand(name);
519                return b;
520            }
521    
522            private void updateEnabledState() {
523                int index = selectedList.getSelectedIndex();
524                upButton.setEnabled(index > 0);
525                downButton.setEnabled(index != -1 && index < selectedList.getModel().getSize() - 1);
526                removeButton.setEnabled(index != -1);
527                addButton.setEnabled(actionsTree.getSelectionCount() > 0);
528            }
529    
530            public void addGui(PreferenceTabbedPane gui) {
531                actionsTree.setCellRenderer(new DefaultTreeCellRenderer() {
532                    @Override
533                    public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded,
534                            boolean leaf, int row, boolean hasFocus) {
535                        DefaultMutableTreeNode node = (DefaultMutableTreeNode) value;
536                        JLabel comp = (JLabel) super.getTreeCellRendererComponent(
537                                tree, value, sel, expanded, leaf, row, hasFocus);
538                        if (node.getUserObject() == null) {
539                            comp.setText(tr("Separator"));
540                            comp.setIcon(ImageProvider.get("preferences/separator"));
541                        }
542                        else if (node.getUserObject() instanceof Action) {
543                            Action action = (Action) node.getUserObject();
544                            comp.setText((String) action.getValue(Action.NAME));
545                            comp.setIcon((Icon) action.getValue(Action.SMALL_ICON));
546                        }
547                        return comp;
548                    }
549                });
550    
551                ListCellRenderer renderer = new DefaultListCellRenderer(){
552                    @Override public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
553                        String s;
554                        Icon i;
555                        ActionDefinition action = (ActionDefinition)value;
556                        if (!action.isSeparator()) {
557                            s = action.getDisplayName();
558                            i = action.getDisplayIcon();
559                        } else {
560                            i = ImageProvider.get("preferences/separator");
561                            s = tr("Separator");
562                        }
563                        JLabel l = (JLabel)super.getListCellRendererComponent(list, s, index, isSelected, cellHasFocus);
564                        l.setIcon(i);
565                        return l;
566                    }
567                };
568                selectedList.setCellRenderer(renderer);
569                selectedList.addListSelectionListener(new ListSelectionListener(){
570                    public void valueChanged(ListSelectionEvent e) {
571                        boolean sel = selectedList.getSelectedIndex() != -1;
572                        if (sel) {
573                            actionsTree.clearSelection();
574                            ActionDefinition action = (ActionDefinition) selected.get(selectedList.getSelectedIndex());
575                            actionParametersModel.setCurrentAction(action);
576                            actionParametersPanel.setVisible(actionParametersModel.getRowCount() > 0);
577                        }
578                        updateEnabledState();
579                    }
580                });
581    
582                selectedList.setDragEnabled(true);
583                selectedList.setTransferHandler(new TransferHandler() {
584                    @Override
585                    protected Transferable createTransferable(JComponent c) {
586                        List<ActionDefinition> actions = new ArrayList<ActionDefinition>();
587                        for (Object o: ((JList)c).getSelectedValues()) {
588                            actions.add((ActionDefinition)o);
589                        }
590                        return new ActionTransferable(actions);
591                    }
592    
593                    @Override
594                    public int getSourceActions(JComponent c) {
595                        return TransferHandler.MOVE;
596                    }
597    
598                    @Override
599                    public boolean canImport(JComponent comp, DataFlavor[] transferFlavors) {
600                        for (DataFlavor f : transferFlavors) {
601                            if (ACTION_FLAVOR.equals(f))
602                                return true;
603                        }
604                        return false;
605                    }
606    
607                    @Override
608                    public void exportAsDrag(JComponent comp, InputEvent e, int action) {
609                        super.exportAsDrag(comp, e, action);
610                        movingComponent = "list";
611                    }
612    
613                    @Override
614                    public boolean importData(JComponent comp, Transferable t) {
615                        try {
616                            int dropIndex = selectedList.locationToIndex(selectedList.getMousePosition(true));
617                            List<?> draggedData = (List<?>) t.getTransferData(ACTION_FLAVOR);
618    
619                            Object leadItem = dropIndex >= 0 ? selected.elementAt(dropIndex) : null;
620                            int dataLength = draggedData.size();
621    
622    
623                            if (leadItem != null) {
624                                for (Object o: draggedData) {
625                                    if (leadItem.equals(o))
626                                        return false;
627    
628                                }
629                            }
630    
631                            int dragLeadIndex = -1;
632                            boolean localDrop = "list".equals(movingComponent);
633    
634                            if (localDrop) {
635                                dragLeadIndex = selected.indexOf(draggedData.get(0));
636                                for (Object o: draggedData) {
637                                    selected.removeElement(o);
638                                }
639                            }
640                            int[] indices = new int[dataLength];
641    
642                            if (localDrop) {
643                                int adjustedLeadIndex = selected.indexOf(leadItem);
644                                int insertionAdjustment = dragLeadIndex <= adjustedLeadIndex ? 1 : 0;
645                                for (int i = 0; i < dataLength; i++) {
646                                    selected.insertElementAt(draggedData.get(i), adjustedLeadIndex + insertionAdjustment + i);
647                                    indices[i] = adjustedLeadIndex + insertionAdjustment + i;
648                                }
649                            } else {
650                                for (int i = 0; i < dataLength; i++) {
651                                    selected.add(dropIndex, draggedData.get(i));
652                                    indices[i] = dropIndex + i;
653                                }
654                            }
655                            selectedList.clearSelection();
656                            selectedList.setSelectedIndices(indices);
657                            movingComponent = "";
658                            return true;
659                        } catch (Exception e) {
660                            e.printStackTrace();
661                        }
662                        return false;
663                    }
664    
665                    @Override
666                    protected void exportDone(JComponent source, Transferable data, int action) {
667                        if (movingComponent.equals("list")) {
668                            try {
669                                List<?> draggedData = (List<?>) data.getTransferData(ACTION_FLAVOR);
670                                boolean localDrop = selected.contains(draggedData.get(0));
671                                if (localDrop) {
672                                    int[] indices = selectedList.getSelectedIndices();
673                                    Arrays.sort(indices);
674                                    for (int i = indices.length - 1; i >= 0; i--) {
675                                        selected.remove(indices[i]);
676                                    }
677                                }
678                            } catch (Exception e) {
679                                e.printStackTrace();
680                            }
681                            movingComponent = "";
682                        }
683                    }
684                });
685    
686                actionsTree.setTransferHandler(new TransferHandler() {
687                    private static final long serialVersionUID = 1L;
688    
689                    @Override
690                    public int getSourceActions( JComponent c ){
691                        return TransferHandler.MOVE;
692                    }
693    
694                    @Override
695                    protected void exportDone(JComponent source, Transferable data, int action) {
696                    }
697    
698                    @Override
699                    protected Transferable createTransferable(JComponent c) {
700                        TreePath[] paths = actionsTree.getSelectionPaths();
701                        List<ActionDefinition> dragActions = new ArrayList<ActionDefinition>();
702                        for (TreePath path : paths) {
703                            DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getLastPathComponent();
704                            Object obj = node.getUserObject();
705                            if (obj == null) {
706                                dragActions.add(ActionDefinition.getSeparator());
707                            }
708                            else if (obj instanceof Action) {
709                                dragActions.add(new ActionDefinition((Action) obj));
710                            }
711                        }
712                        return new ActionTransferable(dragActions);
713                    }
714                });
715                actionsTree.setDragEnabled(true);
716                actionsTree.getSelectionModel().addTreeSelectionListener(new TreeSelectionListener() {
717                    public void valueChanged(TreeSelectionEvent e) {
718                        updateEnabledState();
719                    }
720                });
721    
722                final JPanel left = new JPanel(new GridBagLayout());
723                left.add(new JLabel(tr("Toolbar")), GBC.eol());
724                left.add(new JScrollPane(selectedList), GBC.std().fill(GBC.BOTH));
725    
726                final JPanel right = new JPanel(new GridBagLayout());
727                right.add(new JLabel(tr("Available")), GBC.eol());
728                right.add(new JScrollPane(actionsTree), GBC.eol().fill(GBC.BOTH));
729    
730                final JPanel buttons = new JPanel(new GridLayout(6,1));
731                buttons.add(upButton = createButton("up"));
732                buttons.add(addButton = createButton("<"));
733                buttons.add(removeButton = createButton(">"));
734                buttons.add(downButton = createButton("down"));
735                updateEnabledState();
736    
737                final JPanel p = new JPanel();
738                p.setLayout(new LayoutManager(){
739                    public void addLayoutComponent(String name, Component comp) {}
740                    public void removeLayoutComponent(Component comp) {}
741                    public Dimension minimumLayoutSize(Container parent) {
742                        Dimension l = left.getMinimumSize();
743                        Dimension r = right.getMinimumSize();
744                        Dimension b = buttons.getMinimumSize();
745                        return new Dimension(l.width+b.width+10+r.width,l.height+b.height+10+r.height);
746                    }
747                    public Dimension preferredLayoutSize(Container parent) {
748                        Dimension l = new Dimension(200, 200); //left.getPreferredSize();
749                        Dimension r = new Dimension(200, 200); //right.getPreferredSize();
750                        return new Dimension(l.width+r.width+10+buttons.getPreferredSize().width,Math.max(l.height, r.height));
751                    }
752                    public void layoutContainer(Container parent) {
753                        Dimension d = p.getSize();
754                        Dimension b = buttons.getPreferredSize();
755                        int width = (d.width-10-b.width)/2;
756                        left.setBounds(new Rectangle(0,0,width,d.height));
757                        right.setBounds(new Rectangle(width+10+b.width,0,width,d.height));
758                        buttons.setBounds(new Rectangle(width+5, d.height/2-b.height/2, b.width, b.height));
759                    }
760                });
761                p.add(left);
762                p.add(buttons);
763                p.add(right);
764    
765                actionParametersPanel = new JPanel(new GridBagLayout());
766                actionParametersPanel.add(new JLabel(tr("Action parameters")), GBC.eol().insets(0, 10, 0, 20));
767                actionParametersTable.getColumnModel().getColumn(0).setHeaderValue(tr("Parameter name"));
768                actionParametersTable.getColumnModel().getColumn(1).setHeaderValue(tr("Parameter value"));
769                actionParametersPanel.add(actionParametersTable.getTableHeader(), GBC.eol().fill(GBC.HORIZONTAL));
770                actionParametersPanel.add(actionParametersTable, GBC.eol().fill(GBC.BOTH).insets(0, 0, 0, 10));
771                actionParametersPanel.setVisible(false);
772    
773                JPanel panel = gui.createPreferenceTab(this);
774                panel.add(p, GBC.eol().fill(GBC.BOTH));
775                panel.add(actionParametersPanel, GBC.eol().fill(GBC.HORIZONTAL));
776                selected.removeAllElements();
777                for (ActionDefinition actionDefinition: getDefinedActions()) {
778                    selected.addElement(actionDefinition);
779                }
780            }
781    
782            public boolean ok() {
783                Collection<String> t = new LinkedList<String>();
784                ActionParser parser = new ActionParser(null);
785                for (int i = 0; i < selected.size(); ++i) {
786                    ActionDefinition action = (ActionDefinition)selected.get(i);
787                    if (action.isSeparator()) {
788                        t.add("|");
789                    } else {
790                        String res = parser.saveAction(action);
791                        if(res != null)
792                            t.add(res);
793                    }
794                }
795                if (t.isEmpty()) {
796                    t = Collections.singletonList(EMPTY_TOOLBAR_MARKER);
797                }
798                Main.pref.putCollection("toolbar", t);
799                Main.toolbar.refreshToolbarControl();
800                return false;
801            }
802    
803        }
804    
805        public ToolbarPreferences() {
806            control.setFloatable(false);
807            control.addMouseListener(new PopupMenuLauncher(new ToolbarPopupMenu(null)));
808        }
809    
810        private void loadAction(DefaultMutableTreeNode node, MenuElement menu) {
811            Object userObject = null;
812            MenuElement menuElement = menu;
813            if (menu.getSubElements().length > 0 &&
814                    menu.getSubElements()[0] instanceof JPopupMenu) {
815                menuElement = menu.getSubElements()[0];
816            }
817            for (MenuElement item : menuElement.getSubElements()) {
818                if (item instanceof JMenuItem) {
819                    JMenuItem menuItem = ((JMenuItem)item);
820                    if (menuItem.getAction() != null) {
821                        Action action = menuItem.getAction();
822                        userObject = action;
823                        Object tb = action.getValue("toolbar");
824                        if(tb == null) {
825                            System.out.println(tr("Toolbar action without name: {0}",
826                            action.getClass().getName()));
827                            continue;
828                        } else if (!(tb instanceof String)) {
829                            if(!(tb instanceof Boolean) || (Boolean)tb) {
830                                System.out.println(tr("Strange toolbar value: {0}",
831                                action.getClass().getName()));
832                            }
833                            continue;
834                        } else {
835                            String toolbar = (String) tb;
836                            Action r = actions.get(toolbar);
837                            if(r != null && r != action && !toolbar.startsWith("imagery_")) {
838                                System.out.println(tr("Toolbar action {0} overwritten: {1} gets {2}",
839                                toolbar, r.getClass().getName(), action.getClass().getName()));
840                            }
841                            actions.put(toolbar, action);
842                        }
843                    } else {
844                        userObject = menuItem.getText();
845                    }
846                }
847                DefaultMutableTreeNode newNode = new DefaultMutableTreeNode(userObject);
848                node.add(newNode);
849                loadAction(newNode, item);
850            }
851        }
852    
853        public Action getAction(String s)
854        {
855            Action e = actions.get(s);
856            if(e == null) {
857                e = regactions.get(s);
858            }
859            return e;
860        }
861    
862        private void loadActions() {
863            rootActionsNode.removeAllChildren();
864            loadAction(rootActionsNode, Main.main.menu);
865            for(Map.Entry<String, Action> a : regactions.entrySet())
866            {
867                if(actions.get(a.getKey()) == null) {
868                    rootActionsNode.add(new DefaultMutableTreeNode(a.getValue()));
869                }
870            }
871            rootActionsNode.add(new DefaultMutableTreeNode(null));
872        }
873    
874        private static final String[] deftoolbar = {"open", "save", "download", "upload", "|",
875        "undo", "redo", "|", "dialogs/search", "preference", "|", "splitway", "combineway",
876        "wayflip", "|", "imagery-offset", "|", "tagginggroup_Highways/Streets",
877        "tagginggroup_Highways/Ways", "tagginggroup_Highways/Waypoints",
878        "tagginggroup_Highways/Barriers", "|", "tagginggroup_Transport/Car",
879        "tagginggroup_Transport/Public Transport", "|", "tagginggroup_Facilities/Tourism",
880        "tagginggroup_Facilities/Food+Drinks", "|", "tagginggroup_Man Made/Historic Places", "|",
881        "tagginggroup_Man Made/Man Made"};
882    
883        public static Collection<String> getToolString() {
884    
885            Collection<String> toolStr = Main.pref.getCollection("toolbar", Arrays.asList(deftoolbar));
886            if (toolStr == null || toolStr.size() == 0) {
887                toolStr = Arrays.asList(deftoolbar);
888            }
889            return toolStr;
890        }
891    
892        private Collection<ActionDefinition> getDefinedActions() {
893            loadActions();
894    
895            Map<String, Action> allActions = new HashMap<String, Action>(regactions);
896            allActions.putAll(actions);
897            ActionParser actionParser = new ActionParser(allActions);
898    
899            Collection<ActionDefinition> result = new ArrayList<ActionDefinition>();
900    
901            for (String s : getToolString()) {
902                if (s.equals("|")) {
903                    result.add(ActionDefinition.getSeparator());
904                } else {
905                    ActionDefinition a = actionParser.loadAction(s);
906                    if(a != null) {
907                        result.add(a);
908                    } else {
909                        System.out.println("Could not load tool definition "+s);
910                    }
911                }
912            }
913    
914            return result;
915        }
916    
917        /**
918         * @return The parameter (for better chaining)
919         */
920        public Action register(Action action) {
921            String toolbar = (String) action.getValue("toolbar");
922            if(toolbar == null) {
923                System.out.println(tr("Registered toolbar action without name: {0}",
924                action.getClass().getName()));
925            } else {
926                Action r = regactions.get(toolbar);
927                if(r != null) {
928                    System.out.println(tr("Registered toolbar action {0} overwritten: {1} gets {2}",
929                    toolbar, r.getClass().getName(), action.getClass().getName()));
930                }
931            }
932            regactions.put(toolbar, action);
933            return action;
934        }
935    
936        /**
937         * Parse the toolbar preference setting and construct the toolbar GUI control.
938         *
939         * Call this, if anything has changed in the toolbar settings and you want to refresh
940         * the toolbar content (e.g. after registering actions in a plugin)
941         */
942        public void refreshToolbarControl() {
943            control.removeAll();
944    
945            for (ActionDefinition action : getDefinedActions()) {
946                if (action.isSeparator()) {
947                    control.addSeparator();
948                } else {
949                    final JButton b = control.add(action.getParametrizedAction());
950                    String tt = action.getDisplayTooltip();
951                    if (tt != null && !tt.isEmpty())
952                        b.setToolTipText(tt);
953                    Icon i = action.getDisplayIcon();
954                    if (i != null) {
955                        b.setIcon(i);
956                    } else {
957                        // hide action text if an icon is set later (necessary for delayed/background image loading)
958                        action.getParametrizedAction().addPropertyChangeListener(new PropertyChangeListener() {
959    
960                            @Override
961                            public void propertyChange(PropertyChangeEvent evt) {
962                                if (Action.SMALL_ICON.equals(evt.getPropertyName())) {
963                                    b.setHideActionText(evt.getNewValue() != null);
964                                }
965                            }
966                        });
967                    }
968                    b.addMouseListener(new PopupMenuLauncher( new ToolbarPopupMenu(action)));
969                }
970            }
971            control.setVisible(control.getComponentCount() != 0);
972        }
973    
974        private static DataFlavor ACTION_FLAVOR = new DataFlavor(
975                ActionDefinition.class, "ActionItem");
976    
977    }