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