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