001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui; 003 004import static org.openstreetmap.josm.gui.help.HelpUtil.ht; 005import static org.openstreetmap.josm.tools.I18n.tr; 006import static org.openstreetmap.josm.tools.I18n.trc; 007 008import java.awt.Component; 009import java.awt.GraphicsEnvironment; 010import java.awt.event.KeyEvent; 011import java.util.ArrayList; 012import java.util.Arrays; 013import java.util.Collection; 014import java.util.HashMap; 015import java.util.List; 016import java.util.Locale; 017import java.util.Map; 018import java.util.Objects; 019import java.util.Optional; 020 021import javax.swing.JCheckBoxMenuItem; 022import javax.swing.JMenu; 023import javax.swing.JMenuBar; 024import javax.swing.JMenuItem; 025import javax.swing.JPopupMenu; 026import javax.swing.JSeparator; 027import javax.swing.KeyStroke; 028import javax.swing.event.MenuEvent; 029import javax.swing.event.MenuListener; 030 031import org.openstreetmap.josm.actions.AboutAction; 032import org.openstreetmap.josm.actions.AddNodeAction; 033import org.openstreetmap.josm.actions.AlignInCircleAction; 034import org.openstreetmap.josm.actions.AlignInLineAction; 035import org.openstreetmap.josm.actions.AutoScaleAction; 036import org.openstreetmap.josm.actions.AutoScaleAction.AutoScaleMode; 037import org.openstreetmap.josm.actions.ChangesetManagerToggleAction; 038import org.openstreetmap.josm.actions.CloseChangesetAction; 039import org.openstreetmap.josm.actions.CombineWayAction; 040import org.openstreetmap.josm.actions.CopyAction; 041import org.openstreetmap.josm.actions.CopyCoordinatesAction; 042import org.openstreetmap.josm.actions.CreateCircleAction; 043import org.openstreetmap.josm.actions.CreateMultipolygonAction; 044import org.openstreetmap.josm.actions.DeleteAction; 045import org.openstreetmap.josm.actions.DeleteLayerAction; 046import org.openstreetmap.josm.actions.DialogsToggleAction; 047import org.openstreetmap.josm.actions.DistributeAction; 048import org.openstreetmap.josm.actions.DownloadAction; 049import org.openstreetmap.josm.actions.DownloadAlongWayAction; 050import org.openstreetmap.josm.actions.DownloadNotesInViewAction; 051import org.openstreetmap.josm.actions.DownloadOsmInViewAction; 052import org.openstreetmap.josm.actions.DownloadPrimitiveAction; 053import org.openstreetmap.josm.actions.DownloadReferrersAction; 054import org.openstreetmap.josm.actions.DrawBoundariesOfDownloadedDataAction; 055import org.openstreetmap.josm.actions.DuplicateAction; 056import org.openstreetmap.josm.actions.ExitAction; 057import org.openstreetmap.josm.actions.ExpertToggleAction; 058import org.openstreetmap.josm.actions.FollowLineAction; 059import org.openstreetmap.josm.actions.FullscreenToggleAction; 060import org.openstreetmap.josm.actions.GpxExportAction; 061import org.openstreetmap.josm.actions.HelpAction; 062import org.openstreetmap.josm.actions.HistoryInfoAction; 063import org.openstreetmap.josm.actions.HistoryInfoWebAction; 064import org.openstreetmap.josm.actions.InfoAction; 065import org.openstreetmap.josm.actions.InfoWebAction; 066import org.openstreetmap.josm.actions.JoinAreasAction; 067import org.openstreetmap.josm.actions.JoinNodeWayAction; 068import org.openstreetmap.josm.actions.JosmAction; 069import org.openstreetmap.josm.actions.JumpToAction; 070import org.openstreetmap.josm.actions.MergeLayerAction; 071import org.openstreetmap.josm.actions.MergeNodesAction; 072import org.openstreetmap.josm.actions.MergeSelectionAction; 073import org.openstreetmap.josm.actions.MirrorAction; 074import org.openstreetmap.josm.actions.MoveAction; 075import org.openstreetmap.josm.actions.MoveNodeAction; 076import org.openstreetmap.josm.actions.NewAction; 077import org.openstreetmap.josm.actions.OpenFileAction; 078import org.openstreetmap.josm.actions.OpenLocationAction; 079import org.openstreetmap.josm.actions.OrthogonalizeAction; 080import org.openstreetmap.josm.actions.OrthogonalizeAction.Undo; 081import org.openstreetmap.josm.actions.PasteAction; 082import org.openstreetmap.josm.actions.PasteAtSourcePositionAction; 083import org.openstreetmap.josm.actions.PasteTagsAction; 084import org.openstreetmap.josm.actions.PreferenceToggleAction; 085import org.openstreetmap.josm.actions.PreferencesAction; 086import org.openstreetmap.josm.actions.PurgeAction; 087import org.openstreetmap.josm.actions.RedoAction; 088import org.openstreetmap.josm.actions.ReorderImageryLayersAction; 089import org.openstreetmap.josm.actions.ReportBugAction; 090import org.openstreetmap.josm.actions.RestartAction; 091import org.openstreetmap.josm.actions.ReverseWayAction; 092import org.openstreetmap.josm.actions.SaveAction; 093import org.openstreetmap.josm.actions.SaveAsAction; 094import org.openstreetmap.josm.actions.SearchNotesDownloadAction; 095import org.openstreetmap.josm.actions.SelectAllAction; 096import org.openstreetmap.josm.actions.SelectNonBranchingWaySequencesAction; 097import org.openstreetmap.josm.actions.SessionSaveAsAction; 098import org.openstreetmap.josm.actions.ShowStatusReportAction; 099import org.openstreetmap.josm.actions.SimplifyWayAction; 100import org.openstreetmap.josm.actions.SplitWayAction; 101import org.openstreetmap.josm.actions.TaggingPresetSearchAction; 102import org.openstreetmap.josm.actions.UnGlueAction; 103import org.openstreetmap.josm.actions.UnJoinNodeWayAction; 104import org.openstreetmap.josm.actions.UndoAction; 105import org.openstreetmap.josm.actions.UnselectAllAction; 106import org.openstreetmap.josm.actions.UpdateDataAction; 107import org.openstreetmap.josm.actions.UpdateModifiedAction; 108import org.openstreetmap.josm.actions.UpdateSelectionAction; 109import org.openstreetmap.josm.actions.UploadAction; 110import org.openstreetmap.josm.actions.UploadSelectionAction; 111import org.openstreetmap.josm.actions.ViewportFollowToggleAction; 112import org.openstreetmap.josm.actions.WireframeToggleAction; 113import org.openstreetmap.josm.actions.ZoomInAction; 114import org.openstreetmap.josm.actions.ZoomOutAction; 115import org.openstreetmap.josm.actions.audio.AudioBackAction; 116import org.openstreetmap.josm.actions.audio.AudioFasterAction; 117import org.openstreetmap.josm.actions.audio.AudioFwdAction; 118import org.openstreetmap.josm.actions.audio.AudioNextAction; 119import org.openstreetmap.josm.actions.audio.AudioPlayPauseAction; 120import org.openstreetmap.josm.actions.audio.AudioPrevAction; 121import org.openstreetmap.josm.actions.audio.AudioSlowerAction; 122import org.openstreetmap.josm.actions.mapmode.MapMode; 123import org.openstreetmap.josm.actions.search.SearchAction; 124import org.openstreetmap.josm.data.UndoRedoHandler; 125import org.openstreetmap.josm.gui.dialogs.MenuItemSearchDialog; 126import org.openstreetmap.josm.gui.io.RecentlyOpenedFilesMenu; 127import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeEvent; 128import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeListener; 129import org.openstreetmap.josm.gui.mappaint.MapPaintMenu; 130import org.openstreetmap.josm.gui.preferences.imagery.ImageryPreference; 131import org.openstreetmap.josm.gui.preferences.map.TaggingPresetPreference; 132import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetSearchPrimitiveDialog; 133import org.openstreetmap.josm.spi.preferences.Config; 134import org.openstreetmap.josm.tools.PlatformManager; 135import org.openstreetmap.josm.tools.Shortcut; 136 137/** 138 * This is the JOSM main menu bar. It is overwritten to initialize itself and provide all menu 139 * entries as member variables (sort of collect them). 140 * 141 * It also provides possibilities to attach new menu entries (used by plugins). 142 * 143 * @author Immanuel.Scholz 144 */ 145public class MainMenu extends JMenuBar { 146 147 /** 148 * The possible item groups of the Windows menu. 149 * @see MainMenu#addWithCheckbox 150 */ 151 public enum WINDOW_MENU_GROUP { 152 /** Entries always displayed, at the top */ 153 ALWAYS, 154 /** Entries displayed only for visible toggle dialogs */ 155 TOGGLE_DIALOG, 156 /** Volatile entries displayed at the end */ 157 VOLATILE 158 } 159 160 /* File menu */ 161 /** File / New Layer **/ 162 public final NewAction newAction = new NewAction(); 163 /** File / Open... **/ 164 public final OpenFileAction openFile = new OpenFileAction(); 165 /** File / Open Recent > **/ 166 public final RecentlyOpenedFilesMenu recentlyOpened = new RecentlyOpenedFilesMenu(); 167 /** File / Open Location... **/ 168 public final OpenLocationAction openLocation = new OpenLocationAction(); 169 /** File / Delete Layer **/ 170 public final DeleteLayerAction deleteLayerAction = new DeleteLayerAction(); 171 /** File / Save **/ 172 public final SaveAction save = SaveAction.getInstance(); 173 /** File / Save As... **/ 174 public final SaveAsAction saveAs = SaveAsAction.getInstance(); 175 /** File / Session > Save Session As... **/ 176 public SessionSaveAsAction sessionSaveAs; 177 /** File / Export to GPX... **/ 178 public final GpxExportAction gpxExport = new GpxExportAction(); 179 /** File / Download from OSM... **/ 180 public final DownloadAction download = new DownloadAction(); 181 /** File / Download in current view **/ 182 public final DownloadOsmInViewAction downloadInView = new DownloadOsmInViewAction(); 183 /** File / Download object... **/ 184 public final DownloadPrimitiveAction downloadPrimitive = new DownloadPrimitiveAction(); 185 /** File / Download notes in current view **/ 186 public final DownloadNotesInViewAction downloadNotesInView = DownloadNotesInViewAction.newActionWithNoteIcon(); 187 /** File / Search Notes... **/ 188 public final SearchNotesDownloadAction searchNotes = new SearchNotesDownloadAction(); 189 /** File / Download parent ways/relations... **/ 190 public final DownloadReferrersAction downloadReferrers = new DownloadReferrersAction(); 191 /** File / Close open changesets... **/ 192 public final CloseChangesetAction closeChangesetAction = new CloseChangesetAction(); 193 /** File / Update data **/ 194 public final JosmAction update = new UpdateDataAction(); 195 /** File / Update selection **/ 196 public final JosmAction updateSelection = new UpdateSelectionAction(); 197 /** File / Update modified **/ 198 public final JosmAction updateModified = new UpdateModifiedAction(); 199 /** File / Upload data **/ 200 public final JosmAction upload = new UploadAction(); 201 /** File / Upload selection **/ 202 public final JosmAction uploadSelection = new UploadSelectionAction(); 203 /** File / Restart **/ 204 public final RestartAction restart = new RestartAction(); 205 /** File / Exit **/ 206 public final ExitAction exit = new ExitAction(); 207 208 /* Edit menu */ 209 /** Edit / Undo... */ 210 public final UndoAction undo = new UndoAction(); 211 /** Edit / Redo */ 212 public final RedoAction redo = new RedoAction(); 213 /** Edit / Copy */ 214 public final CopyAction copy = new CopyAction(); 215 /** Edit / Copy Coordinates */ 216 public final JosmAction copyCoordinates = new CopyCoordinatesAction(); 217 /** Edit / Paste */ 218 public final PasteAction paste = new PasteAction(); 219 /** Edit / Paste at source */ 220 private final PasteAtSourcePositionAction pasteAtSource = new PasteAtSourcePositionAction(); 221 /** Edit / Paste Tags */ 222 public final PasteTagsAction pasteTags = new PasteTagsAction(); 223 /** Edit / Duplicate */ 224 public final DuplicateAction duplicate = new DuplicateAction(); 225 /** Edit / Delete */ 226 public final DeleteAction delete = new DeleteAction(); 227 /** Edit / Purge... */ 228 public final JosmAction purge = new PurgeAction(); 229 /** Edit / Merge layer */ 230 public final MergeLayerAction merge = new MergeLayerAction(); 231 /** Edit / Merge selection */ 232 public final MergeSelectionAction mergeSelected = new MergeSelectionAction(); 233 /** Edit / Search... */ 234 public final SearchAction search = new SearchAction(); 235 /** Edit / Preferences */ 236 public final PreferencesAction preferences = new PreferencesAction(); 237 238 /* View menu */ 239 /** View / Wireframe View */ 240 public final WireframeToggleAction wireFrameToggleAction = new WireframeToggleAction(); 241 /** View / Hatch area outside download */ 242 public final DrawBoundariesOfDownloadedDataAction drawBoundariesOfDownloadedDataAction = new DrawBoundariesOfDownloadedDataAction(); 243 /** View / Advanced info */ 244 public final InfoAction info = new InfoAction(); 245 /** View / Advanced info (web) */ 246 public final InfoWebAction infoweb = new InfoWebAction(); 247 /** View / History */ 248 public final HistoryInfoAction historyinfo = new HistoryInfoAction(); 249 /** View / History (web) */ 250 public final HistoryInfoWebAction historyinfoweb = new HistoryInfoWebAction(); 251 /** View / "Zoom to"... actions */ 252 public final Map<String, AutoScaleAction> autoScaleActions = new HashMap<>(); 253 /** View / Jump to position */ 254 public final JumpToAction jumpToAct = new JumpToAction(); 255 256 /* Tools menu */ 257 /** Tools / Split Way */ 258 public final SplitWayAction splitWay = new SplitWayAction(); 259 /** Tools / Combine Way */ 260 public final CombineWayAction combineWay = new CombineWayAction(); 261 /** Tools / Reverse Ways */ 262 public final ReverseWayAction reverseWay = new ReverseWayAction(); 263 /** Tools / Simplify Way */ 264 public final SimplifyWayAction simplifyWay = new SimplifyWayAction(); 265 /** Tools / Align Nodes in Circle */ 266 public final AlignInCircleAction alignInCircle = new AlignInCircleAction(); 267 /** Tools / Align Nodes in Line */ 268 public final AlignInLineAction alignInLine = new AlignInLineAction(); 269 /** Tools / Distribute Nodes */ 270 public final DistributeAction distribute = new DistributeAction(); 271 /** Tools / Orthogonalize Shape */ 272 public final OrthogonalizeAction ortho = new OrthogonalizeAction(); 273 /** Orthogonalize undo. Action is not shown in the menu. Only triggered by shortcut */ 274 public final Undo orthoUndo = new Undo(); 275 /** Tools / Mirror */ 276 public final MirrorAction mirror = new MirrorAction(); 277 /** Tools / Follow line */ 278 public final FollowLineAction followLine = new FollowLineAction(); 279 /** Tools / Add Node... */ 280 public final AddNodeAction addNode = new AddNodeAction(); 281 /** Tools / Move Node... */ 282 public final MoveNodeAction moveNode = new MoveNodeAction(); 283 /** Tools / Create Circle */ 284 public final CreateCircleAction createCircle = new CreateCircleAction(); 285 /** Tools / Merge Nodes */ 286 public final MergeNodesAction mergeNodes = new MergeNodesAction(); 287 /** Tools / Join Node to Way */ 288 public final JoinNodeWayAction joinNodeWay = JoinNodeWayAction.createJoinNodeToWayAction(); 289 /** Tools / Join Way to Node */ 290 public final JoinNodeWayAction moveNodeOntoWay = JoinNodeWayAction.createMoveNodeOntoWayAction(); 291 /** Tools / Disconnect Node from Way */ 292 public final UnJoinNodeWayAction unJoinNodeWay = new UnJoinNodeWayAction(); 293 /** Tools / Unglue Ways */ 294 public final UnGlueAction unglueNodes = new UnGlueAction(); 295 /** Tools / Join overlapping Areas */ 296 public final JoinAreasAction joinAreas = new JoinAreasAction(); 297 /** Tools / Create multipolygon */ 298 public final CreateMultipolygonAction createMultipolygon = new CreateMultipolygonAction(false); 299 /** Tools / Update multipolygon */ 300 public final CreateMultipolygonAction updateMultipolygon = new CreateMultipolygonAction(true); 301 /** Tools / Download along way */ 302 public final DownloadAlongWayAction downloadAlongWay = new DownloadAlongWayAction(); 303 304 /* Selection menu */ 305 /** Selection / Select All */ 306 public final SelectAllAction selectAll = new SelectAllAction(); 307 /** Selection / Unselect All */ 308 public final UnselectAllAction unselectAll = new UnselectAllAction(); 309 /** Selection / Non-branching way sequences */ 310 public final SelectNonBranchingWaySequencesAction nonBranchingWaySequences = new SelectNonBranchingWaySequencesAction(); 311 312 /* Audio menu */ 313 /** Audio / Play/Pause */ 314 public final JosmAction audioPlayPause = new AudioPlayPauseAction(); 315 /** Audio / Next marker */ 316 public final JosmAction audioNext = new AudioNextAction(); 317 /** Audio / Previous Marker */ 318 public final JosmAction audioPrev = new AudioPrevAction(); 319 /** Audio / Forward */ 320 public final JosmAction audioFwd = new AudioFwdAction(); 321 /** Audio / Back */ 322 public final JosmAction audioBack = new AudioBackAction(); 323 /** Audio / Faster */ 324 public final JosmAction audioFaster = new AudioFasterAction(); 325 /** Audio / Slower */ 326 public final JosmAction audioSlower = new AudioSlowerAction(); 327 328 /* Windows Menu */ 329 /** Windows / Changeset Manager */ 330 public final ChangesetManagerToggleAction changesetManager = new ChangesetManagerToggleAction(); 331 332 /* Help menu */ 333 /** Help / Help */ 334 public final HelpAction help = new HelpAction(); 335 /** Help / About */ 336 public final AboutAction about = new AboutAction(); 337 /** Help / Show Status Report */ 338 public final ShowStatusReportAction statusreport = new ShowStatusReportAction(); 339 /** Help / Report bug */ 340 public final ReportBugAction reportbug = new ReportBugAction(); 341 342 /** 343 * fileMenu contains I/O actions 344 */ 345 public final JMenu fileMenu = addMenu("File", /* I18N: mnemonic: F */ trc("menu", "File"), KeyEvent.VK_F, 0, ht("/Menu/File")); 346 /** 347 * editMenu contains editing actions 348 */ 349 public final JMenu editMenu = addMenu("Edit", /* I18N: mnemonic: E */ trc("menu", "Edit"), KeyEvent.VK_E, 1, ht("/Menu/Edit")); 350 /** 351 * viewMenu contains display actions (zoom, map styles, etc.) 352 */ 353 public final JMenu viewMenu = addMenu("View", /* I18N: mnemonic: V */ trc("menu", "View"), KeyEvent.VK_V, 2, ht("/Menu/View")); 354 /** 355 * modeMenu contains map modes 356 */ 357 public final JMenu modeMenu = addMenu(new ModeMenu(), /* I18N: mnemonic: M */ trc("menu", "Mode"), KeyEvent.VK_M, 3, ht("/Menu/Mode")); 358 /** 359 * toolsMenu contains different geometry manipulation actions from JOSM core (most used) 360 * The plugins should use other menus 361 */ 362 public final JMenu toolsMenu = addMenu("Tools", /* I18N: mnemonic: T */ trc("menu", "Tools"), KeyEvent.VK_T, 4, ht("/Menu/Tools")); 363 /** 364 * moreToolsMenu contains geometry-related actions from all the plugins 365 * @since 6082 (moved from Utilsplugin2) 366 */ 367 // CHECKSTYLE.OFF: LineLength 368 public final JMenu moreToolsMenu = addMenu("More tools", /* I18N: mnemonic: O */ trc("menu", "More tools"), KeyEvent.VK_O, 5, ht("/Menu/MoreTools")); 369 /** 370 * dataMenu contains plugin actions that are related to certain tagging schemes (addressing opening hours), 371 * importing external data and using external web APIs 372 * @since 6082 373 */ 374 public final JMenu dataMenu = addMenu("Data", /* I18N: mnemonic: D */ trc("menu", "Data"), KeyEvent.VK_D, 6, ht("/Menu/Data")); 375 /** 376 * selectionMenu contains all actions related to selecting different objects 377 * @since 6082 (moved from Utilsplugin2) 378 */ 379 public final JMenu selectionMenu = addMenu("Selection", /* I18N: mnemonic: N */ trc("menu", "Selection"), KeyEvent.VK_N, 7, ht("/Menu/Selection")); 380 /** 381 * presetsMenu contains presets actions (search, presets tree) 382 */ 383 public final JMenu presetsMenu = addMenu("Presets", /* I18N: mnemonic: P */ trc("menu", "Presets"), KeyEvent.VK_P, 8, ht("/Menu/Presets")); 384 /** 385 * submenu in Imagery menu that contains plugin-managed additional imagery layers 386 * @since 6097 387 */ 388 public final JMenu imagerySubMenu = new JMenu(tr("More...")); 389 /** 390 * imageryMenu contains all imagery-related actions 391 */ 392 public final ImageryMenu imageryMenu = addMenu(new ImageryMenu(imagerySubMenu), /* untranslated name */ "Imagery", KeyEvent.VK_I, 9, ht("/Menu/Imagery")); 393 // CHECKSTYLE.ON: LineLength 394 /** 395 * gpsMenu contains all plugin actions that are related 396 * to using GPS data, including opening, uploading and real-time tracking 397 * @since 6082 398 */ 399 public final JMenu gpsMenu = addMenu("GPS", /* I18N: mnemonic: G */ trc("menu", "GPS"), KeyEvent.VK_G, 10, ht("/Menu/GPS")); 400 /** the window menu is split into several groups. The first is for windows that can be opened from 401 * this menu any time, e.g. the changeset editor. The second group is for toggle dialogs and the third 402 * group is for currently open windows that cannot be toggled, e.g. relation editors. It's recommended 403 * to use WINDOW_MENU_GROUP to determine the group integer. 404 */ 405 public final WindowMenu windowMenu = addMenu(new WindowMenu(), /* untranslated name */ "Windows", KeyEvent.VK_W, 11, ht("/ToggleDialogs")); 406 407 /** 408 * audioMenu contains all audio-related actions. Be careful, this menu is not guaranteed to be displayed at all 409 */ 410 public JMenu audioMenu; 411 /** 412 * helpMenu contains JOSM general actions (Help, About, etc.) 413 */ 414 public final JMenu helpMenu = addMenu("Help", /* I18N: mnemonic: H */ trc("menu", "Help"), KeyEvent.VK_H, 12, ht("/Menu/Help")); 415 416 private static final int defaultMenuPos = 12; 417 418 /** Move the selection up */ 419 public final JosmAction moveUpAction = new MoveAction(MoveAction.Direction.UP); 420 /** Move the selection down */ 421 public final JosmAction moveDownAction = new MoveAction(MoveAction.Direction.DOWN); 422 /** Move the selection left */ 423 public final JosmAction moveLeftAction = new MoveAction(MoveAction.Direction.LEFT); 424 /** Move the selection right */ 425 public final JosmAction moveRightAction = new MoveAction(MoveAction.Direction.RIGHT); 426 427 /** Reorder imagery layers */ 428 public final ReorderImageryLayersAction reorderImageryLayersAction = new ReorderImageryLayersAction(); 429 430 /** Search tagging presets */ 431 public final TaggingPresetSearchAction presetSearchAction = new TaggingPresetSearchAction(); 432 /** Search objects by their tagging preset */ 433 public final TaggingPresetSearchPrimitiveDialog.Action presetSearchPrimitiveAction = new TaggingPresetSearchPrimitiveDialog.Action(); 434 /** Toggle visibility of dialogs panel */ 435 public final DialogsToggleAction dialogsToggleAction = new DialogsToggleAction(); 436 /** Toggle the full-screen mode */ 437 public FullscreenToggleAction fullscreenToggleAction; 438 439 /** this menu listener hides unnecessary JSeparators in a menu list but does not remove them. 440 * If at a later time the separators are required, they will be made visible again. Intended 441 * usage is make menus not look broken if separators are used to group the menu and some of 442 * these groups are empty. 443 */ 444 public static final MenuListener menuSeparatorHandler = new MenuListener() { 445 @Override 446 public void menuCanceled(MenuEvent e) { 447 // Do nothing 448 } 449 450 @Override 451 public void menuDeselected(MenuEvent e) { 452 // Do nothing 453 } 454 455 @Override 456 public void menuSelected(MenuEvent a) { 457 if (!(a.getSource() instanceof JMenu)) 458 return; 459 final JPopupMenu m = ((JMenu) a.getSource()).getPopupMenu(); 460 for (int i = 0; i < m.getComponentCount()-1; i++) { 461 // hide separator if the next menu item is one as well 462 if (m.getComponent(i) instanceof JSeparator && m.getComponent(i+1) instanceof JSeparator) { 463 ((JSeparator) m.getComponent(i)).setVisible(false); 464 } 465 } 466 // hide separator at the end of the menu 467 if (m.getComponent(m.getComponentCount()-1) instanceof JSeparator) { 468 ((JSeparator) m.getComponent(m.getComponentCount()-1)).setVisible(false); 469 } 470 } 471 }; 472 473 /** 474 * @return the default position of new top-level menus 475 * @since 6088 476 */ 477 public int getDefaultMenuPos() { 478 return defaultMenuPos; 479 } 480 481 /** 482 * Add a JosmAction at the end of a menu. 483 * 484 * This method handles all the shortcut handling. It also makes sure that actions that are 485 * handled by the OS are not duplicated on the menu. 486 * @param menu the menu to add the action to 487 * @param action the action that should get a menu item 488 * @return the created menu item 489 */ 490 public static JMenuItem add(JMenu menu, JosmAction action) { 491 return add(menu, action, false); 492 } 493 494 /** 495 * Add a JosmAction at the end of a menu. 496 * 497 * This method handles all the shortcut handling. It also makes sure that actions that are 498 * handled by the OS are not duplicated on the menu. 499 * @param menu the menu to add the action to 500 * @param action the action that should get a menu item 501 * @param isExpert whether the entry should only be visible if the expert mode is activated 502 * @return the created menu item 503 */ 504 public static JMenuItem add(JMenu menu, JosmAction action, boolean isExpert) { 505 return add(menu, action, isExpert, null); 506 } 507 508 /** 509 * Add a JosmAction at the end of a menu. 510 * 511 * This method handles all the shortcut handling. It also makes sure that actions that are 512 * handled by the OS are not duplicated on the menu. 513 * @param menu the menu to add the action to 514 * @param action the action that should get a menu item 515 * @param isExpert whether the entry should only be visible if the expert mode is activated 516 * @param index an integer specifying the position at which to add the action 517 * @return the created menu item 518 */ 519 public static JMenuItem add(JMenu menu, JosmAction action, boolean isExpert, Integer index) { 520 if (action.getShortcut().isAutomatic()) 521 return null; 522 final JMenuItem menuitem; 523 if (index == null) { 524 menuitem = menu.add(action); 525 } else { 526 menuitem = menu.insert(action, index); 527 } 528 if (isExpert) { 529 ExpertToggleAction.addVisibilitySwitcher(menuitem); 530 } 531 KeyStroke ks = action.getShortcut().getKeyStroke(); 532 if (ks != null) { 533 menuitem.setAccelerator(ks); 534 } 535 // some menus are hidden before they are populated with some items by plugins 536 if (!menu.isVisible()) menu.setVisible(true); 537 return menuitem; 538 } 539 540 /** 541 * Add the JosmAction {@code actionToBeInserted} directly below {@code existingMenuEntryAction}. 542 * 543 * This method handles all the shortcut handling. It also makes sure that actions that are 544 * handled by the OS are not duplicated on the menu. 545 * @param menu the menu to add the action to 546 * @param actionToBeInserted the action that should get a menu item directly below {@code existingMenuEntryAction} 547 * @param isExpert whether the entry should only be visible if the expert mode is activated 548 * @param existingMenuEntryAction an action already added to the menu {@code menu}, 549 * the action {@code actionToBeInserted} is added directly below 550 * @return the created menu item 551 */ 552 public static JMenuItem addAfter(JMenu menu, JosmAction actionToBeInserted, boolean isExpert, JosmAction existingMenuEntryAction) { 553 int i = 0; 554 for (Component c : menu.getMenuComponents()) { 555 if (c instanceof JMenuItem && ((JMenuItem) c).getAction() == existingMenuEntryAction) { 556 break; 557 } 558 i++; 559 } 560 return add(menu, actionToBeInserted, isExpert, i + 1); 561 } 562 563 /** 564 * Add a JosmAction to a menu. 565 * 566 * This method handles all the shortcut handling. It also makes sure that actions that are 567 * handled by the OS are not duplicated on the menu. 568 * @param <E> group item enum type 569 * @param menu to add the action to 570 * @param action the action that should get a menu item 571 * @param group the item should be added to. Groups are split by a separator. null will add the item to the end. 572 * @return The created menu item 573 */ 574 public static <E extends Enum<E>> JMenuItem add(JMenu menu, JosmAction action, Enum<E> group) { 575 if (action.getShortcut().isAutomatic()) 576 return null; 577 int i = group != null ? getInsertionIndexForGroup(menu, group.ordinal(), false) : -1; 578 JMenuItem menuitem = (JMenuItem) menu.add(new JMenuItem(action), i); 579 KeyStroke ks = action.getShortcut().getKeyStroke(); 580 if (ks != null) { 581 menuitem.setAccelerator(ks); 582 } 583 return menuitem; 584 } 585 586 /** 587 * Add a JosmAction to a menu and automatically prints accelerator if available. 588 * Also adds a checkbox that may be toggled. 589 * @param <E> group enum item type 590 * @param menu to add the action to 591 * @param action the action that should get a menu item 592 * @param group the item should be added to. Groups are split by a separator. Use 593 * one of the enums that are defined for some of the menus to tell in which 594 * group the item should go. 595 * @return The created menu item 596 */ 597 public static <E extends Enum<E>> JCheckBoxMenuItem addWithCheckbox(JMenu menu, JosmAction action, Enum<E> group) { 598 return addWithCheckbox(menu, action, group, false, false); 599 } 600 601 /** 602 * Add a JosmAction to a menu and automatically prints accelerator if available. 603 * Also adds a checkbox that may be toggled. 604 * @param <E> group enum item type 605 * @param menu to add the action to 606 * @param action the action that should get a menu item 607 * @param group the item should be added to. Groups are split by a separator. Use 608 * one of the enums that are defined for some of the menus to tell in which 609 * group the item should go. null will add the item to the end. 610 * @param isEntryExpert whether the entry should only be visible if the expert mode is activated 611 * @param isGroupSeparatorExpert whether the group separator should only be visible if the expert mode is activated 612 * @return The created menu item 613 * @since 15633 614 */ 615 public static <E extends Enum<E>> JCheckBoxMenuItem addWithCheckbox(JMenu menu, JosmAction action, Enum<E> group, 616 boolean isEntryExpert, boolean isGroupSeparatorExpert) { 617 int i = group != null ? getInsertionIndexForGroup(menu, group.ordinal(), isGroupSeparatorExpert) : -1; 618 return addWithCheckbox(menu, action, i, isEntryExpert); 619 } 620 621 /** 622 * Add a JosmAction to a menu and automatically prints accelerator if available. 623 * Also adds a checkbox that may be toggled. 624 * @param <E> group enum item type 625 * @param menu to add the action to 626 * @param action the action that should get a menu item 627 * @param i the item position in the menu. -1 will add the item to the end. 628 * @param isEntryExpert whether the entry should only be visible if the expert mode is activated 629 * @return The created menu item 630 * @since 15655 631 */ 632 public static <E extends Enum<E>> JCheckBoxMenuItem addWithCheckbox(JMenu menu, JosmAction action, int i, boolean isEntryExpert) { 633 final JCheckBoxMenuItem mi = new JCheckBoxMenuItem(action); 634 final KeyStroke ks = action.getShortcut().getKeyStroke(); 635 if (ks != null) { 636 mi.setAccelerator(ks); 637 } 638 if (isEntryExpert) { 639 ExpertToggleAction.addVisibilitySwitcher(mi); 640 } 641 return (JCheckBoxMenuItem) menu.add(mi, i); 642 } 643 644 /** 645 * Finds the correct insertion index for a given group and adds separators if necessary 646 * @param menu menu 647 * @param group group number 648 * @param isGroupSeparatorExpert whether the added separators should only be visible if the expert mode is activated 649 * @return correct insertion index 650 */ 651 private static int getInsertionIndexForGroup(JMenu menu, int group, boolean isGroupSeparatorExpert) { 652 if (group < 0) 653 return -1; 654 // look for separator that *ends* the group (or stop at end of menu) 655 int i; 656 for (i = 0; i < menu.getItemCount() && group >= 0; i++) { 657 if (menu.getItem(i) == null) { 658 group--; 659 } 660 } 661 // insert before separator that ends the group 662 if (group < 0) { 663 i--; 664 } 665 // not enough separators have been found, add them 666 while (group > 0) { 667 menu.addSeparator(); 668 if (isGroupSeparatorExpert) { 669 ExpertToggleAction.addVisibilitySwitcher(menu.getMenuComponent(menu.getMenuComponentCount() - 1)); 670 } 671 group--; 672 i++; 673 } 674 return i; 675 } 676 677 /** 678 * Creates a menu and adds it on the given position to the main menu. 679 * 680 * @param name the untranslated name (used as identifier for shortcut registration) 681 * @param translatedName the translated menu name (use {@code I18n.trc("menu", name)} to allow better internationalization 682 * @param mnemonicKey the mnemonic key to register 683 * @param position the position in the main menu 684 * @param relativeHelpTopic the relative help topic 685 * @return the newly created menu 686 */ 687 public JMenu addMenu(String name, String translatedName, int mnemonicKey, int position, String relativeHelpTopic) { 688 final JMenu menu = new JMenu(translatedName); 689 if (!GraphicsEnvironment.isHeadless()) { 690 MenuScroller.setScrollerFor(menu); 691 } 692 return addMenu(menu, name, mnemonicKey, position, relativeHelpTopic); 693 } 694 695 /** 696 * Adds the given menu on the given position to the main menu. 697 * @param <T> menu type 698 * 699 * @param menu the menu to add 700 * @param name the untranslated name (used as identifier for shortcut registration) 701 * @param mnemonicKey the mnemonic key to register 702 * @param position the position in the main menu 703 * @param relativeHelpTopic the relative help topic 704 * @return the given {@code }menu} 705 */ 706 public <T extends JMenu> T addMenu(T menu, String name, int mnemonicKey, int position, String relativeHelpTopic) { 707 Shortcut.registerShortcut("menu:" + name, tr("Menu: {0}", name), mnemonicKey, 708 Shortcut.MNEMONIC).setMnemonic(menu); 709 add(menu, position); 710 menu.putClientProperty("help", relativeHelpTopic); 711 return menu; 712 } 713 714 /** 715 * Initialize the main menu. 716 * @since 10340 717 */ 718 // CHECKSTYLE.OFF: ExecutableStatementCountCheck 719 public void initialize() { 720 moreToolsMenu.setVisible(false); 721 dataMenu.setVisible(false); 722 gpsMenu.setVisible(false); 723 724 add(fileMenu, newAction); 725 add(fileMenu, openFile); 726 fileMenu.add(recentlyOpened); 727 add(fileMenu, openLocation); 728 add(fileMenu, deleteLayerAction); 729 fileMenu.addSeparator(); 730 add(fileMenu, save); 731 add(fileMenu, saveAs); 732 sessionSaveAs = new SessionSaveAsAction(); 733 ExpertToggleAction.addVisibilitySwitcher(fileMenu.add(sessionSaveAs)); 734 add(fileMenu, gpxExport, true); 735 fileMenu.addSeparator(); 736 add(fileMenu, download); 737 add(fileMenu, downloadInView, true); 738 add(fileMenu, downloadAlongWay); 739 add(fileMenu, downloadPrimitive); 740 add(fileMenu, searchNotes); 741 add(fileMenu, downloadNotesInView); 742 add(fileMenu, downloadReferrers); 743 add(fileMenu, update); 744 add(fileMenu, updateSelection); 745 add(fileMenu, updateModified); 746 fileMenu.addSeparator(); 747 add(fileMenu, upload); 748 add(fileMenu, uploadSelection); 749 Component sep = new JPopupMenu.Separator(); 750 fileMenu.add(sep); 751 ExpertToggleAction.addVisibilitySwitcher(sep); 752 add(fileMenu, closeChangesetAction, true); 753 fileMenu.addSeparator(); 754 add(fileMenu, restart); 755 add(fileMenu, exit); 756 757 add(editMenu, undo); 758 UndoRedoHandler.getInstance().addCommandQueueListener(undo); 759 add(editMenu, redo); 760 UndoRedoHandler.getInstance().addCommandQueueListener(redo); 761 editMenu.addSeparator(); 762 add(editMenu, copy); 763 add(editMenu, copyCoordinates, true); 764 add(editMenu, paste); 765 add(editMenu, pasteAtSource, true); 766 add(editMenu, pasteTags); 767 add(editMenu, duplicate); 768 add(editMenu, delete); 769 add(editMenu, purge, true); 770 editMenu.addSeparator(); 771 add(editMenu, merge); 772 add(editMenu, mergeSelected); 773 editMenu.addSeparator(); 774 add(editMenu, search); 775 add(editMenu, presetSearchPrimitiveAction); 776 editMenu.addSeparator(); 777 add(editMenu, preferences); 778 779 // -- wireframe toggle action 780 final JCheckBoxMenuItem wireframe = new JCheckBoxMenuItem(wireFrameToggleAction); 781 viewMenu.add(wireframe); 782 wireframe.setAccelerator(wireFrameToggleAction.getShortcut().getKeyStroke()); 783 wireFrameToggleAction.addButtonModel(wireframe.getModel()); 784 final JCheckBoxMenuItem hatchAreaOutsideDownloadMenuItem = drawBoundariesOfDownloadedDataAction.getCheckbox(); 785 viewMenu.add(hatchAreaOutsideDownloadMenuItem); 786 ExpertToggleAction.addVisibilitySwitcher(hatchAreaOutsideDownloadMenuItem); 787 788 viewMenu.add(new MapPaintMenu()); 789 viewMenu.addSeparator(); 790 add(viewMenu, new ZoomInAction()); 791 add(viewMenu, new ZoomOutAction()); 792 viewMenu.addSeparator(); 793 for (AutoScaleMode mode : AutoScaleMode.values()) { 794 AutoScaleAction autoScaleAction = new AutoScaleAction(mode); 795 autoScaleActions.put(mode.getEnglishLabel(), autoScaleAction); 796 add(viewMenu, autoScaleAction); 797 } 798 799 // -- viewport follow toggle action 800 ViewportFollowToggleAction viewportFollowToggleAction = new ViewportFollowToggleAction(); 801 final JCheckBoxMenuItem vft = new JCheckBoxMenuItem(viewportFollowToggleAction); 802 ExpertToggleAction.addVisibilitySwitcher(vft); 803 viewMenu.add(vft); 804 vft.setAccelerator(viewportFollowToggleAction.getShortcut().getKeyStroke()); 805 viewportFollowToggleAction.addButtonModel(vft.getModel()); 806 807 if (PlatformManager.getPlatform().canFullscreen()) { 808 // -- fullscreen toggle action 809 fullscreenToggleAction = new FullscreenToggleAction(); 810 final JCheckBoxMenuItem fullscreen = new JCheckBoxMenuItem(fullscreenToggleAction); 811 viewMenu.addSeparator(); 812 viewMenu.add(fullscreen); 813 fullscreen.setAccelerator(fullscreenToggleAction.getShortcut().getKeyStroke()); 814 fullscreenToggleAction.addButtonModel(fullscreen.getModel()); 815 } 816 817 add(viewMenu, jumpToAct, true); 818 viewMenu.addSeparator(); 819 add(viewMenu, info); 820 add(viewMenu, infoweb); 821 add(viewMenu, historyinfo); 822 add(viewMenu, historyinfoweb); 823 viewMenu.addSeparator(); 824 viewMenu.add(new PreferenceToggleAction(tr("Main toolbar"), 825 tr("Toggles the visibility of the main toolbar (i.e., the horizontal toolbar)"), 826 "toolbar.visible", true).getCheckbox()); 827 viewMenu.add(new PreferenceToggleAction(tr("Edit toolbar"), 828 tr("Toggles the visibility of the edit toolbar (i.e., the vertical tool)"), 829 "sidetoolbar.visible", true).getCheckbox()); 830 // -- dialogs panel toggle action 831 final JCheckBoxMenuItem dialogsToggle = new JCheckBoxMenuItem(dialogsToggleAction); 832 dialogsToggle.setAccelerator(dialogsToggleAction.getShortcut().getKeyStroke()); 833 dialogsToggleAction.addButtonModel(dialogsToggle.getModel()); 834 viewMenu.add(dialogsToggle); 835 viewMenu.addSeparator(); 836 // -- expert mode toggle action 837 final JCheckBoxMenuItem expertItem = new JCheckBoxMenuItem(ExpertToggleAction.getInstance()); 838 viewMenu.add(expertItem); 839 ExpertToggleAction.getInstance().addButtonModel(expertItem.getModel()); 840 841 add(presetsMenu, presetSearchAction); 842 add(presetsMenu, presetSearchPrimitiveAction); 843 add(presetsMenu, PreferencesAction.forPreferenceSubTab(tr("Preset preferences"), 844 tr("Click to open the tagging presets tab in the preferences"), TaggingPresetPreference.class)); 845 presetsMenu.addSeparator(); 846 847 add(imageryMenu, reorderImageryLayersAction); 848 add(imageryMenu, PreferencesAction.forPreferenceTab(tr("Imagery preferences..."), 849 tr("Click to open the imagery tab in the preferences"), ImageryPreference.class)); 850 851 add(selectionMenu, selectAll); 852 add(selectionMenu, unselectAll); 853 add(selectionMenu, nonBranchingWaySequences); 854 855 add(toolsMenu, splitWay); 856 add(toolsMenu, combineWay); 857 toolsMenu.addSeparator(); 858 add(toolsMenu, reverseWay); 859 add(toolsMenu, simplifyWay); 860 toolsMenu.addSeparator(); 861 add(toolsMenu, alignInCircle); 862 add(toolsMenu, alignInLine); 863 add(toolsMenu, distribute); 864 add(toolsMenu, ortho); 865 add(toolsMenu, mirror, true); 866 toolsMenu.addSeparator(); 867 add(toolsMenu, followLine, true); 868 add(toolsMenu, addNode, true); 869 add(toolsMenu, moveNode, true); 870 add(toolsMenu, createCircle); 871 toolsMenu.addSeparator(); 872 add(toolsMenu, mergeNodes); 873 add(toolsMenu, joinNodeWay); 874 add(toolsMenu, moveNodeOntoWay); 875 add(toolsMenu, unJoinNodeWay); 876 add(toolsMenu, unglueNodes); 877 toolsMenu.addSeparator(); 878 add(toolsMenu, joinAreas); 879 add(toolsMenu, createMultipolygon); 880 add(toolsMenu, updateMultipolygon); 881 882 // -- changeset manager toggle action 883 final JCheckBoxMenuItem mi = MainMenu.addWithCheckbox(windowMenu, changesetManager, 884 WINDOW_MENU_GROUP.ALWAYS, true, false); 885 changesetManager.addButtonModel(mi.getModel()); 886 887 if (!Config.getPref().getBoolean("audio.menuinvisible", false)) { 888 showAudioMenu(true); 889 } 890 891 Config.getPref().addPreferenceChangeListener(e -> { 892 if ("audio.menuinvisible".equals(e.getKey())) { 893 showAudioMenu(!Boolean.parseBoolean(e.getNewValue().toString())); 894 } 895 }); 896 897 add(helpMenu, new MenuItemSearchDialog.Action()); 898 helpMenu.addSeparator(); 899 add(helpMenu, statusreport); 900 add(helpMenu, reportbug); 901 helpMenu.addSeparator(); 902 903 add(helpMenu, help); 904 add(helpMenu, about); 905 906 windowMenu.addMenuListener(menuSeparatorHandler); 907 908 new EditLayerMenuEnabler(Arrays.asList(modeMenu, presetsMenu)); 909 } 910 // CHECKSTYLE.ON: ExecutableStatementCountCheck 911 912 /** 913 * Search main menu for items with {@code textToFind} in title. 914 * @param textToFind The text to find 915 * @param skipPresets whether to skip the {@link #presetsMenu} in the search 916 * @return not null list of found menu items. 917 */ 918 public List<JMenuItem> findMenuItems(String textToFind, boolean skipPresets) { 919 // Explicitly use default locale in this case, because we're looking for translated strings 920 textToFind = textToFind.toLowerCase(Locale.getDefault()); 921 List<JMenuItem> result = new ArrayList<>(); 922 for (int i = 0; i < getMenuCount(); i++) { 923 if (getMenu(i) != null && (!skipPresets || presetsMenu != getMenu(i))) { 924 findMenuItems(getMenu(i), textToFind, result); 925 } 926 } 927 return result; 928 } 929 930 /** 931 * Returns the {@link JCheckBoxMenuItem} for the given {@link MapMode}. 932 * @param mode map mode 933 * @return the {@code JCheckBoxMenuItem} for the given {@code MapMode} 934 * @since 15438 935 */ 936 public Optional<JCheckBoxMenuItem> findMapModeMenuItem(MapMode mode) { 937 return Arrays.stream(modeMenu.getMenuComponents()) 938 .filter(m -> m instanceof JCheckBoxMenuItem) 939 .map(m -> (JCheckBoxMenuItem) m) 940 .filter(m -> Objects.equals(mode, m.getAction())) 941 .findFirst(); 942 } 943 944 /** 945 * Recursive walker for menu items. Only menu items with action are selected. If menu item 946 * contains {@code textToFind} it's appended to result. 947 * @param menu menu in which search will be performed 948 * @param textToFind The text to find 949 * @param result resulting list of menu items 950 */ 951 private static void findMenuItems(final JMenu menu, final String textToFind, final List<JMenuItem> result) { 952 for (int i = 0; i < menu.getItemCount(); i++) { 953 JMenuItem menuItem = menu.getItem(i); 954 if (menuItem == null) continue; 955 956 // Explicitly use default locale in this case, because we're looking for translated strings 957 if (menuItem.getAction() != null && menuItem.getText().toLowerCase(Locale.getDefault()).contains(textToFind)) { 958 result.add(menuItem); 959 } 960 961 // Go recursive if needed 962 if (menuItem instanceof JMenu) { 963 findMenuItems((JMenu) menuItem, textToFind, result); 964 } 965 } 966 } 967 968 protected void showAudioMenu(boolean showMenu) { 969 if (showMenu && audioMenu == null) { 970 audioMenu = addMenu("Audio", /* I18N: mnemonic: U */ trc("menu", "Audio"), KeyEvent.VK_U, defaultMenuPos, ht("/Menu/Audio")); 971 add(audioMenu, audioPlayPause); 972 add(audioMenu, audioNext); 973 add(audioMenu, audioPrev); 974 add(audioMenu, audioFwd); 975 add(audioMenu, audioBack); 976 add(audioMenu, audioSlower); 977 add(audioMenu, audioFaster); 978 validate(); 979 } else if (!showMenu && audioMenu != null) { 980 remove(audioMenu); 981 audioMenu.removeAll(); 982 audioMenu = null; 983 validate(); 984 } 985 } 986 987 static class EditLayerMenuEnabler implements ActiveLayerChangeListener { 988 private final Collection<JMenu> menus; 989 990 EditLayerMenuEnabler(Collection<JMenu> menus) { 991 this.menus = Objects.requireNonNull(menus); 992 MainApplication.getLayerManager().addAndFireActiveLayerChangeListener(this); 993 } 994 995 @Override 996 public void activeOrEditLayerChanged(ActiveLayerChangeEvent e) { 997 menus.forEach(m -> m.setEnabled(e.getSource().getEditLayer() != null)); 998 } 999 } 1000}