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