/* * Copyright 2017 Laszlo Balazs-Csiki * * This file is part of Pixelitor. Pixelitor is free software: you * can redistribute it and/or modify it under the terms of the GNU * General Public License, version 3 as published by the Free * Software Foundation. * * Pixelitor is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Pixelitor. If not, see <http://www.gnu.org/licenses/>. */ package pixelitor.menus; import com.bric.util.JVM; import com.jhlabs.composite.MultiplyComposite; import pixelitor.AppLogic; import pixelitor.Build; import pixelitor.Composition; import pixelitor.NewImage; import pixelitor.TipsOfTheDay; import pixelitor.automate.AutoPaint; import pixelitor.automate.BatchFilterWizard; import pixelitor.automate.BatchResize; import pixelitor.colors.FgBgColors; import pixelitor.colors.palette.ColorSwatchClickHandler; import pixelitor.colors.palette.FullPalette; import pixelitor.colors.palette.VariationsPanel; import pixelitor.filters.*; import pixelitor.filters.animation.TweenWizard; import pixelitor.filters.comp.EnlargeCanvas; import pixelitor.filters.comp.Flip; import pixelitor.filters.comp.Rotate; import pixelitor.filters.convolve.Convolve; import pixelitor.filters.gui.ResizePanel; import pixelitor.filters.jhlabsproxies.*; import pixelitor.filters.levels.Levels; import pixelitor.filters.lookup.ColorBalance; import pixelitor.filters.lookup.Luminosity; import pixelitor.filters.painters.TextFilter; import pixelitor.gui.Desktop; import pixelitor.gui.HistogramsPanel; import pixelitor.gui.ImageComponent; import pixelitor.gui.ImageComponents; import pixelitor.gui.Navigator; import pixelitor.gui.PixelitorWindow; import pixelitor.gui.utils.GUIUtils; import pixelitor.gui.utils.PerformanceTestingDialog; import pixelitor.history.History; import pixelitor.io.FileChoosers; import pixelitor.io.OpenSaveManager; import pixelitor.io.OptimizedJpegSavePanel; import pixelitor.layers.AddAdjLayerAction; import pixelitor.layers.AddNewLayerAction; import pixelitor.layers.ContentLayer; import pixelitor.layers.DeleteActiveLayerAction; import pixelitor.layers.Drawable; import pixelitor.layers.DuplicateLayerAction; import pixelitor.layers.ImageLayer; import pixelitor.layers.Layer; import pixelitor.layers.LayerMask; import pixelitor.layers.LayerMaskAddType; import pixelitor.layers.LayerMoveAction; import pixelitor.layers.MaskFromColorRangePanel; import pixelitor.layers.MaskViewMode; import pixelitor.layers.TextLayer; import pixelitor.menus.edit.CopyAction; import pixelitor.menus.edit.CopySource; import pixelitor.menus.edit.FadeMenuItem; import pixelitor.menus.edit.PasteAction; import pixelitor.menus.edit.PasteDestination; import pixelitor.menus.file.AnimGifExport; import pixelitor.menus.file.OpenRasterExportPanel; import pixelitor.menus.file.RecentFilesMenu; import pixelitor.menus.file.ScreenCaptureAction; import pixelitor.menus.help.AboutDialog; import pixelitor.menus.help.UpdatesCheck; import pixelitor.menus.view.ShowHideAllAction; import pixelitor.menus.view.ShowHideHistogramsAction; import pixelitor.menus.view.ShowHideLayersAction; import pixelitor.menus.view.ShowHideStatusBarAction; import pixelitor.menus.view.ShowHideToolsAction; import pixelitor.menus.view.ZoomMenu; import pixelitor.selection.SelectionActions; import pixelitor.tools.brushes.CopyBrush; import pixelitor.utils.AppPreferences; import pixelitor.utils.FilterCreator; import pixelitor.utils.Messages; import pixelitor.utils.Tests3x3; import pixelitor.utils.Utils; import pixelitor.utils.debug.AppNode; import pixelitor.utils.test.Events; import pixelitor.utils.test.FilterTests; import pixelitor.utils.test.RandomGUITest; import pixelitor.utils.test.SplashImageCreator; import pixelitor.utils.test.ToolTests; import javax.swing.*; import java.awt.BorderLayout; import java.awt.Composite; import java.awt.Graphics2D; import java.awt.Toolkit; import java.awt.event.InputEvent; import java.awt.event.KeyEvent; import java.awt.image.BufferedImage; import java.lang.management.ManagementFactory; import static pixelitor.Composition.ImageChangeActions.FULL; import static pixelitor.colors.FillType.BACKGROUND; import static pixelitor.colors.FillType.FOREGROUND; import static pixelitor.colors.FillType.TRANSPARENT; import static pixelitor.filters.comp.Flip.Direction.HORIZONTAL; import static pixelitor.filters.comp.Flip.Direction.VERTICAL; import static pixelitor.filters.comp.Rotate.SpecialAngle.ANGLE_180; import static pixelitor.filters.comp.Rotate.SpecialAngle.ANGLE_270; import static pixelitor.filters.comp.Rotate.SpecialAngle.ANGLE_90; import static pixelitor.filters.jhlabsproxies.JHMotionBlur.Mode.MOTION_BLUR; import static pixelitor.filters.jhlabsproxies.JHMotionBlur.Mode.SPIN_ZOOM_BLUR; import static pixelitor.gui.ImageComponents.getActiveCompOrNull; import static pixelitor.gui.ImageComponents.getActiveLayerOrNull; import static pixelitor.menus.EnabledIf.ACTION_ENABLED; import static pixelitor.menus.EnabledIf.CAN_REPEAT_OPERATION; import static pixelitor.menus.EnabledIf.REDO_POSSIBLE; import static pixelitor.menus.EnabledIf.UNDO_POSSIBLE; import static pixelitor.menus.MenuAction.AllowedLayerType.HAS_LAYER_MASK; import static pixelitor.menus.MenuAction.AllowedLayerType.IS_TEXT_LAYER; /** * The menu bar of the app */ public class MenuBar extends JMenuBar { public static final int MENU_SHORTCUT_KEY_MASK = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(); public static final KeyStroke CTRL_1 = KeyStroke.getKeyStroke('1', MENU_SHORTCUT_KEY_MASK); public static final KeyStroke CTRL_2 = KeyStroke.getKeyStroke('2', MENU_SHORTCUT_KEY_MASK); public static final KeyStroke CTRL_3 = KeyStroke.getKeyStroke('3', MENU_SHORTCUT_KEY_MASK); public static final KeyStroke CTRL_4 = KeyStroke.getKeyStroke('4', MENU_SHORTCUT_KEY_MASK); private static final KeyStroke CTRL_B = KeyStroke.getKeyStroke('B', MENU_SHORTCUT_KEY_MASK); private static final KeyStroke CTRL_C = KeyStroke.getKeyStroke('C', MENU_SHORTCUT_KEY_MASK); private static final KeyStroke CTRL_D = KeyStroke.getKeyStroke('D', MENU_SHORTCUT_KEY_MASK); private static final KeyStroke CTRL_E = KeyStroke.getKeyStroke('E', MENU_SHORTCUT_KEY_MASK); private static final KeyStroke CTRL_F = KeyStroke.getKeyStroke('F', MENU_SHORTCUT_KEY_MASK); // private static final KeyStroke CTRL_G = KeyStroke.getKeyStroke('G', MENU_SHORTCUT_KEY_MASK); private static final KeyStroke CTRL_H = KeyStroke.getKeyStroke('H', MENU_SHORTCUT_KEY_MASK); private static final KeyStroke CTRL_I = KeyStroke.getKeyStroke('I', MENU_SHORTCUT_KEY_MASK); private static final KeyStroke CTRL_J = KeyStroke.getKeyStroke('J', MENU_SHORTCUT_KEY_MASK); // private static final KeyStroke CTRL_K = KeyStroke.getKeyStroke('K', MENU_SHORTCUT_KEY_MASK); private static final KeyStroke CTRL_L = KeyStroke.getKeyStroke('L', MENU_SHORTCUT_KEY_MASK); private static final KeyStroke CTRL_M = KeyStroke.getKeyStroke('M', MENU_SHORTCUT_KEY_MASK); private static final KeyStroke CTRL_N = KeyStroke.getKeyStroke('N', MENU_SHORTCUT_KEY_MASK); private static final KeyStroke CTRL_O = KeyStroke.getKeyStroke('O', MENU_SHORTCUT_KEY_MASK); private static final KeyStroke CTRL_R = KeyStroke.getKeyStroke('R', MENU_SHORTCUT_KEY_MASK); private static final KeyStroke CTRL_S = KeyStroke.getKeyStroke('S', MENU_SHORTCUT_KEY_MASK); private static final KeyStroke CTRL_T = KeyStroke.getKeyStroke('T', MENU_SHORTCUT_KEY_MASK); private static final KeyStroke CTRL_U = KeyStroke.getKeyStroke('U', MENU_SHORTCUT_KEY_MASK); private static final KeyStroke CTRL_V = KeyStroke.getKeyStroke('V', MENU_SHORTCUT_KEY_MASK); private static final KeyStroke CTRL_Z = KeyStroke.getKeyStroke('Z', MENU_SHORTCUT_KEY_MASK); private static final KeyStroke CTRL_W = KeyStroke.getKeyStroke('W', MENU_SHORTCUT_KEY_MASK); private static final KeyStroke CTRL_SHIFT_S = KeyStroke.getKeyStroke('S', MENU_SHORTCUT_KEY_MASK + InputEvent.SHIFT_MASK); private static final KeyStroke CTRL_ALT_W = KeyStroke.getKeyStroke('W', MENU_SHORTCUT_KEY_MASK | InputEvent.ALT_MASK); private static final KeyStroke CTRL_SHIFT_Z = KeyStroke.getKeyStroke('Z', InputEvent.SHIFT_MASK + MENU_SHORTCUT_KEY_MASK); private static final KeyStroke CTRL_SHIFT_F = KeyStroke.getKeyStroke('F', MENU_SHORTCUT_KEY_MASK + InputEvent.SHIFT_MASK); private static final KeyStroke CTRL_SHIFT_C = KeyStroke.getKeyStroke('C', MENU_SHORTCUT_KEY_MASK + InputEvent.SHIFT_MASK); private static final KeyStroke CTRL_SHIFT_V = KeyStroke.getKeyStroke('V', MENU_SHORTCUT_KEY_MASK + InputEvent.SHIFT_MASK); private static final KeyStroke CTRL_ALT_V = KeyStroke.getKeyStroke('V', MENU_SHORTCUT_KEY_MASK + InputEvent.ALT_MASK); private static final KeyStroke CTRL_ALT_I = KeyStroke.getKeyStroke('I', MENU_SHORTCUT_KEY_MASK | InputEvent.ALT_MASK); private static final KeyStroke ALT_BACKSPACE = KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SPACE, InputEvent.ALT_MASK); private static final KeyStroke CTRL_BACKSPACE = KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SPACE, MENU_SHORTCUT_KEY_MASK); private static final KeyStroke CTRL_SHIFT_I = KeyStroke.getKeyStroke('I', MENU_SHORTCUT_KEY_MASK + InputEvent.SHIFT_MASK); private static final KeyStroke T = KeyStroke.getKeyStroke('T'); private static final KeyStroke CTRL_SHIFT_ALT_E = KeyStroke.getKeyStroke('E', MENU_SHORTCUT_KEY_MASK + InputEvent.ALT_MASK + InputEvent.SHIFT_MASK); private static final KeyStroke CTRL_CLOSE_BRACKET = KeyStroke.getKeyStroke(']', MENU_SHORTCUT_KEY_MASK); private static final KeyStroke CTRL_OPEN_BRACKET = KeyStroke.getKeyStroke('[', MENU_SHORTCUT_KEY_MASK); private static final KeyStroke CTRL_SHIFT_CLOSE_BRACKET = KeyStroke.getKeyStroke(']', MENU_SHORTCUT_KEY_MASK + InputEvent.SHIFT_MASK); private static final KeyStroke CTRL_SHIFT_OPEN_BRACKET = KeyStroke.getKeyStroke('[', MENU_SHORTCUT_KEY_MASK + InputEvent.SHIFT_MASK); private static final KeyStroke ALT_CLOSE_BRACKET = KeyStroke.getKeyStroke(']', InputEvent.ALT_MASK); private static final KeyStroke ALT_OPEN_BRACKET = KeyStroke.getKeyStroke('[', InputEvent.ALT_MASK); private static final KeyStroke F5 = KeyStroke.getKeyStroke(KeyEvent.VK_F5, 0); private static final KeyStroke F6 = KeyStroke.getKeyStroke(KeyEvent.VK_F6, 0); private static final KeyStroke F7 = KeyStroke.getKeyStroke(KeyEvent.VK_F7, 0); private static final KeyStroke TAB = KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0); private static final KeyStroke CTRL_ALT_R = KeyStroke.getKeyStroke('R', MENU_SHORTCUT_KEY_MASK + InputEvent.ALT_MASK); public MenuBar(PixelitorWindow pw) { this.add(createFileMenu(pw)); this.add(createEditMenu()); this.add(createLayerMenu(pw)); this.add(createSelectMenu()); this.add(createImageMenu()); this.add(createColorMenu()); this.add(createFilterMenu()); this.add(createViewMenu(pw)); if (Build.CURRENT != Build.FINAL) { this.add(createDevelopMenu(pw)); } this.add(createHelpMenu(pw)); } private static JMenu createFileMenu(PixelitorWindow pw) { PMenu fileMenu = new PMenu("File", 'F'); // new image fileMenu.buildAction(NewImage.getAction()) .alwaysEnabled() .withKey(CTRL_N) .add(); fileMenu.buildAction(new MenuAction("Open...") { @Override public void onClick() { FileChoosers.open(); } }).alwaysEnabled().withKey(CTRL_O).add(); // recent files JMenu recentFiles = RecentFilesMenu.getInstance(); fileMenu.add(recentFiles); fileMenu.addSeparator(); fileMenu.addActionWithKey(new MenuAction("Save") { @Override public void onClick() { OpenSaveManager.save(false); } }, CTRL_S); fileMenu.addActionWithKey(new MenuAction("Save As...") { @Override public void onClick() { OpenSaveManager.save(true); } }, CTRL_SHIFT_S); fileMenu.addAction(new MenuAction("Export Optimized JPEG...") { @Override public void onClick() { BufferedImage image = ImageComponents.getActiveCompositeImage(); OptimizedJpegSavePanel.showInDialog(image, pw); } }); fileMenu.addAction(new MenuAction("Export OpenRaster...") { @Override public void onClick() { OpenRasterExportPanel.showInDialog(pw); } }); fileMenu.addSeparator(); fileMenu.addAction(new MenuAction("Export Layer Animation...") { @Override public void onClick() { AnimGifExport.start(pw); } }); fileMenu.addAction(new ImageLayerAction("Export Tweening Animation") { @Override protected void process(ImageLayer layer) { new TweenWizard(layer).start(pw); } }); fileMenu.addSeparator(); // close fileMenu.addActionWithKey(new MenuAction("Close") { @Override public void onClick() { OpenSaveManager.warnAndCloseImage(ImageComponents.getActiveIC()); } }, CTRL_W); // close all fileMenu.addActionWithKey(new MenuAction("Close All") { @Override public void onClick() { OpenSaveManager.warnAndCloseAllImages(); } }, CTRL_ALT_W); // reload fileMenu.addActionWithKey(new MenuAction("Reload") { @Override public void onClick() { ImageComponents.reloadActiveFromFile(); } }, F5); fileMenu.addSeparator(); fileMenu.add(createAutomateSubmenu(pw)); if (!JVM.isMac) { fileMenu.addAlwaysEnabledAction(new ScreenCaptureAction()); } fileMenu.addSeparator(); // exit String exitName = JVM.isMac ? "Quit" : "Exit"; fileMenu.addAlwaysEnabledAction(new MenuAction(exitName) { @Override public void onClick() { AppLogic.exitApp(pw); } }); return fileMenu; } private static JMenu createAutomateSubmenu(PixelitorWindow pw) { PMenu sub = new PMenu("Automate"); sub.addAlwaysEnabledAction(new MenuAction("Batch Resize...") { @Override public void onClick() { BatchResize.start(); } }); sub.buildAction(new ImageLayerAction("Batch Filter") { @Override protected void process(ImageLayer layer) { new BatchFilterWizard(layer).start(pw); } }).add(); sub.addAction(new MenuAction("Export Layers to PNG...") { @Override public void onClick() { OpenSaveManager.exportLayersToPNG(); } }); sub.addAction(new ImageLayerAction("Auto Paint") { @Override protected void process(ImageLayer layer) { AutoPaint.showDialog(layer); } }); return sub; } private static JMenu createEditMenu() { PMenu editMenu = new PMenu("Edit", 'E'); // last op editMenu.buildAction(RepeatLast.INSTANCE) .enableIf(CAN_REPEAT_OPERATION) .withKey(CTRL_F) .add(); editMenu.addSeparator(); // undo editMenu.buildAction(History.UNDO_ACTION) .enableIf(UNDO_POSSIBLE) .withKey(CTRL_Z) .add(); // redo editMenu.buildAction(History.REDO_ACTION) .enableIf(REDO_POSSIBLE) .withKey(CTRL_SHIFT_Z) .add(); // fade FadeMenuItem.INSTANCE.setAccelerator(CTRL_SHIFT_F); editMenu.add(FadeMenuItem.INSTANCE); editMenu.addSeparator(); // copy editMenu.addActionWithKey(new CopyAction(CopySource.LAYER), CTRL_C); editMenu.addActionWithKey(new CopyAction(CopySource.COMPOSITE), CTRL_SHIFT_C); // paste editMenu.buildAction(new PasteAction(PasteDestination.NEW_IMAGE)).alwaysEnabled().withKey(CTRL_V).add(); editMenu.addActionWithKey(new PasteAction(PasteDestination.NEW_LAYER), CTRL_SHIFT_V); editMenu.addActionWithKey(new PasteAction(PasteDestination.MASK), CTRL_ALT_V); editMenu.addSeparator(); // preferences Action preferencesAction = new MenuAction("Preferences...") { @Override public void onClick() { AppPreferences.Panel.showInDialog(); } }; editMenu.add(preferencesAction); return editMenu; } private static JMenu createLayerMenu(PixelitorWindow pw) { PMenu layersMenu = new PMenu("Layer", 'L'); layersMenu.add(AddNewLayerAction.INSTANCE); layersMenu.add(DeleteActiveLayerAction.INSTANCE); layersMenu.addActionWithKey(DuplicateLayerAction.INSTANCE, CTRL_J); layersMenu.addSeparator(); layersMenu.addActionWithKey(new MenuAction("Merge Down") { @Override public void onClick() { getActiveCompOrNull().mergeDown(true); } }, CTRL_E); layersMenu.addAction(new MenuAction("Flatten Image") { @Override public void onClick() { getActiveCompOrNull().flattenImage(true); } }); layersMenu.addActionWithKey(new MenuAction("New Layer from Composite") { @Override public void onClick() { getActiveCompOrNull().addNewLayerFromComposite("Composite"); } }, CTRL_SHIFT_ALT_E); layersMenu.add(createLayerStackSubmenu()); layersMenu.add(createLayerMaskSubmenu()); layersMenu.add(createTextLayerSubmenu(pw)); if (Build.enableAdjLayers) { layersMenu.add(createAdjustmentLayersSubmenu()); } return layersMenu; } private static JMenu createLayerStackSubmenu() { PMenu sub = new PMenu("Layer Stack"); sub.addActionWithKey(LayerMoveAction.INSTANCE_UP, CTRL_CLOSE_BRACKET); sub.addActionWithKey(LayerMoveAction.INSTANCE_DOWN, CTRL_OPEN_BRACKET); sub.addActionWithKey(new MenuAction("Layer to Top") { @Override public void onClick() { getActiveCompOrNull().moveActiveLayerToTop(); } }, CTRL_SHIFT_CLOSE_BRACKET); sub.addActionWithKey(new MenuAction("Layer to Bottom") { @Override public void onClick() { getActiveCompOrNull().moveActiveLayerToBottom(); } }, CTRL_SHIFT_OPEN_BRACKET); sub.addSeparator(); sub.addActionWithKey(new MenuAction("Raise Layer Selection") { @Override public void onClick() { Composition comp = getActiveCompOrNull(); comp.moveLayerSelectionUp(); } }, ALT_CLOSE_BRACKET); sub.addActionWithKey(new MenuAction("Lower Layer Selection") { @Override public void onClick() { getActiveCompOrNull().moveLayerSelectionDown(); } }, ALT_OPEN_BRACKET); return sub; } private static JMenu createLayerMaskSubmenu() { PMenu sub = new PMenu("Layer Mask"); sub.addAction(new MenuAction("Add White (Reveal All)") { @Override public void onClick() { getActiveLayerOrNull().addMask(LayerMaskAddType.REVEAL_ALL); } }); sub.addAction(new MenuAction("Add Black (Hide All)") { @Override public void onClick() { getActiveLayerOrNull().addMask(LayerMaskAddType.HIDE_ALL); } }); sub.addAction(new MenuAction("Add from Selection") { @Override public void onClick() { getActiveLayerOrNull().addMask(LayerMaskAddType.REVEAL_SELECTION); } }); sub.addActionWithKey(new GetImageAction(MaskFromColorRangePanel.NAME, true, false) { @Override protected void process(Layer layer, BufferedImage image) { MaskFromColorRangePanel.showInDialog(layer, image); } }, CTRL_M); sub.addAction(new MenuAction("Delete", HAS_LAYER_MASK) { @Override public void onClick() { ImageComponent ic = ImageComponents.getActiveIC(); Composition comp = ic.getComp(); Layer layer = comp.getActiveLayer(); layer.deleteMask(true); comp.imageChanged(FULL); } }); sub.addAction(new MenuAction("Apply", HAS_LAYER_MASK) { @Override public void onClick() { ImageComponent ic = ImageComponents.getActiveIC(); Layer layer = ic.getComp().getActiveLayer(); if (!(layer instanceof ImageLayer)) { Messages.showNotImageLayerError(); return; } ((ImageLayer) layer).applyLayerMask(true); // TODO actually this should not be necessary layer.getComp().imageChanged(FULL); } }); sub.addSeparator(); MaskViewMode.NORMAL.addToMenu(sub); MaskViewMode.SHOW_MASK.addToMenu(sub); MaskViewMode.EDIT_MASK.addToMenu(sub); MaskViewMode.RUBYLITH.addToMenu(sub); return sub; } private static JMenu createTextLayerSubmenu(PixelitorWindow pw) { PMenu sub = new PMenu("Text Layer"); sub.addActionWithKey(new MenuAction("New...") { @Override public void onClick() { TextLayer.createNew(pw); } }, T); sub.addActionWithKey(new MenuAction("Edit...", IS_TEXT_LAYER) { @Override public void onClick() { ImageComponents.onActiveTextLayer(textLayer -> textLayer.edit(pw)); } }, CTRL_T); sub.addAction(new MenuAction("Rasterize", IS_TEXT_LAYER) { @Override public void onClick() { ImageComponents.onActiveTextLayer(TextLayer::replaceWithRasterized); } }); return sub; } private static JMenu createAdjustmentLayersSubmenu() { PMenu sub = new PMenu("New Adjustment Layer"); sub.addAction(new MenuAction("Invert Adjustment") { // TODO not "Invert" because of assertj test lookup confusion @Override public void onClick() { AddAdjLayerAction.INSTANCE.actionPerformed(null); } }); return sub; } private static JMenu createSelectMenu() { PMenu selectMenu = new PMenu("Select", 'S'); selectMenu.buildAction(SelectionActions.getDeselectAction()).enableIf(ACTION_ENABLED).withKey(CTRL_D).add(); selectMenu.buildAction(SelectionActions.getShowHideSelectionAction()).enableIf(ACTION_ENABLED).withKey(CTRL_H).add(); selectMenu.buildAction(SelectionActions.getInvertSelectionAction()).enableIf(ACTION_ENABLED).withKey(CTRL_SHIFT_I).add(); selectMenu.buildAction(SelectionActions.getModifyAction()).enableIf(ACTION_ENABLED).add(); selectMenu.addSeparator(); selectMenu.buildAction(SelectionActions.getTraceWithBrush()).enableIf(ACTION_ENABLED).add(); selectMenu.buildAction(SelectionActions.getTraceWithEraser()).enableIf(ACTION_ENABLED).add(); return selectMenu; } private static JMenu createImageMenu() { PMenu imageMenu = new PMenu("Image", 'I'); // crop imageMenu.buildAction(SelectionActions.getCropAction()) .enableIf(ACTION_ENABLED).add(); // resize imageMenu.addActionWithKey(new MenuAction("Resize...") { @Override public void onClick() { ResizePanel.resizeActiveImage(); } }, CTRL_ALT_I); imageMenu.addAction(new MenuAction("Duplicate") { @Override public void onClick() { ImageComponents.duplicateActive(); } }); imageMenu.addSeparator(); imageMenu.addAction(EnlargeCanvas.getAction()); imageMenu.addAction(new MenuAction("Layer to Canvas Size") { @Override public void onClick() { getActiveCompOrNull().activeLayerToCanvasSize(); } }); imageMenu.addSeparator(); // rotate imageMenu.addAction(new Rotate(ANGLE_90)); imageMenu.addAction(new Rotate(ANGLE_180)); imageMenu.addAction(new Rotate(ANGLE_270)); imageMenu.addSeparator(); // flip imageMenu.addAction(new Flip(HORIZONTAL)); imageMenu.addAction(new Flip(VERTICAL)); return imageMenu; } private static JMenu createColorMenu() { PMenu colorsMenu = new PMenu("Color", 'C'); colorsMenu.buildFA("Color Balance", ColorBalance::new).withKey(CTRL_B).add(); colorsMenu.buildFA(HueSat.NAME, HueSat::new).withKey(CTRL_U).add(); colorsMenu.buildFA(Colorize.NAME, Colorize::new).add(); colorsMenu.buildFA("Levels", Levels::new).withKey(CTRL_L).add(); // colorsMenu.buildFA("Levels 2", Levels2::new).add(); colorsMenu.buildFA(Brightness.NAME, Brightness::new).add(); colorsMenu.buildFA(Solarize.NAME, Solarize::new).add(); colorsMenu.buildFA(Sepia.NAME, Sepia::new).add(); colorsMenu.buildFA("Invert", Invert::new).noGUI().withKey(CTRL_I).add(); colorsMenu.buildFA(ChannelInvert.NAME, ChannelInvert::new).add(); colorsMenu.buildFA("Channel Mixer", ChannelMixer::new).add(); colorsMenu.add(createExtractChannelsSubmenu()); colorsMenu.add(createReduceColorsSubmenu()); colorsMenu.add(createFillSubmenu()); return colorsMenu; } private static JMenu createExtractChannelsSubmenu() { PMenu sub = new PMenu("Extract Channels"); sub.addFA("Extract Channel", ExtractChannel::new); sub.addSeparator(); sub.buildFA(Luminosity.NAME, Luminosity::new).noGUI().extract().add(); FilterAction extractValueChannel = ExtractChannelFilter.getValueChannelFA(); sub.addFA(extractValueChannel); FilterAction desaturateChannel = ExtractChannelFilter.getDesaturateChannelFA(); sub.addFA(desaturateChannel); sub.addSeparator(); FilterAction getHue = ExtractChannelFilter.getHueChannelFA(); sub.addFA(getHue); FilterAction getHuiInColors = ExtractChannelFilter.getHueInColorsChannelFA(); sub.addFA(getHuiInColors); FilterAction getSat = ExtractChannelFilter.getSaturationChannelFA(); sub.addFA(getSat); return sub; } private static JMenu createReduceColorsSubmenu() { PMenu sub = new PMenu("Reduce Colors"); sub.addFA(JHQuantize.NAME, JHQuantize::new); sub.addFA("Posterize", Posterize::new); sub.addFA(Threshold.NAME, Threshold::new); sub.addSeparator(); sub.addFA(JHTriTone.NAME, JHTriTone::new); sub.addFA(GradientMap.NAME, GradientMap::new); sub.addSeparator(); sub.addFA(JHColorHalftone.NAME, JHColorHalftone::new); sub.addFA(JHDither.NAME, JHDither::new); return sub; } private static JMenu createFillSubmenu() { PMenu sub = new PMenu("Fill with"); sub.buildFA(FOREGROUND.createFillFilterAction()).withKey(ALT_BACKSPACE).add(); sub.buildFA(BACKGROUND.createFillFilterAction()).withKey(CTRL_BACKSPACE).add(); sub.addFA(TRANSPARENT.createFillFilterAction()); sub.buildFA(ColorWheel.NAME, ColorWheel::new).withFillListName().add(); sub.buildFA(JHFourColorGradient.NAME, JHFourColorGradient::new) .withFillListName().add(); sub.buildFA("Starburst", Starburst::new).withFillListName().add(); return sub; } private static JMenu createFilterMenu() { PMenu filterMenu = new PMenu("Filter", 'T'); filterMenu.add(createBlurSharpenSubmenu()); filterMenu.add(createDistortSubmenu()); filterMenu.add(createDislocateSubmenu()); filterMenu.add(createLightSubmenu()); filterMenu.add(createNoiseSubmenu()); filterMenu.add(createRenderSubmenu()); filterMenu.add(createArtisticSubmenu()); filterMenu.add(createFindEdgesSubmenu()); filterMenu.add(createOtherSubmenu()); // TODO does it still make sense to add the old text filter? filterMenu.buildFA(TextFilter.createFilterAction()).add(); return filterMenu; } private static JMenu createBlurSharpenSubmenu() { PMenu sub = new PMenu("Blur/Sharpen"); sub.addFA(JHBoxBlur.NAME, JHBoxBlur::new); // sub.addFA(FastBlur.NAME, FastBlur::new); sub.addFA(JHFocus.NAME, JHFocus::new); sub.addFA(JHGaussianBlur.NAME, JHGaussianBlur::new); sub.addFA(JHLensBlur.NAME, JHLensBlur::new); sub.addFA(MOTION_BLUR.createFilterAction()); sub.addFA(JHSmartBlur.NAME, JHSmartBlur::new); sub.addFA(SPIN_ZOOM_BLUR.createFilterAction()); sub.addSeparator(); sub.addFA(JHUnsharpMask.NAME, JHUnsharpMask::new); return sub; } private static JMenu createDistortSubmenu() { PMenu sub = new PMenu("Distort"); sub.addFA(SwirlPinchBulge.NAME, SwirlPinchBulge::new); sub.addFA(CircleToSquare.NAME, CircleToSquare::new); sub.addFA(JHPerspective.NAME, JHPerspective::new); sub.addSeparator(); sub.addFA(JHLensOverImage.NAME, JHLensOverImage::new); sub.addFA(Magnify.NAME, Magnify::new); sub.addSeparator(); sub.addFA(JHTurbulentDistortion.NAME, JHTurbulentDistortion::new); sub.addFA(JHUnderWater.NAME, JHUnderWater::new); sub.addFA(JHWaterRipple.NAME, JHWaterRipple::new); sub.addFA(JHWaves.NAME, JHWaves::new); sub.addFA(AngularWaves.NAME, AngularWaves::new); sub.addFA(RadialWaves.NAME, RadialWaves::new); sub.addSeparator(); sub.addFA(GlassTiles.NAME, GlassTiles::new); sub.addFA(PolarTiles.NAME, PolarTiles::new); sub.addFA(JHFrostedGlass.NAME, JHFrostedGlass::new); sub.addSeparator(); sub.addFA(LittlePlanet.NAME, LittlePlanet::new); sub.addFA(JHPolarCoordinates.NAME, JHPolarCoordinates::new); sub.addFA(JHWrapAroundArc.NAME, JHWrapAroundArc::new); return sub; } private static JMenu createDislocateSubmenu() { PMenu sub = new PMenu("Dislocate"); sub.addFA(DrunkVision.NAME, DrunkVision::new); sub.addFA(JHKaleidoscope.NAME, JHKaleidoscope::new); sub.addFA(JHOffset.NAME, JHOffset::new); sub.addFA(Mirror.NAME, Mirror::new); sub.addFA(Slice.NAME, Slice::new); sub.addFA(JHVideoFeedback.NAME, JHVideoFeedback::new); return sub; } private static JMenu createLightSubmenu() { PMenu sub = new PMenu("Light"); sub.addFA(Flashlight.NAME, Flashlight::new); sub.addFA(JHGlint.NAME, JHGlint::new); sub.addFA(JHGlow.NAME, JHGlow::new); sub.addFA(JHRays.NAME, JHRays::new); sub.addFA(JHSparkle.NAME, JHSparkle::new); return sub; } private static JMenu createNoiseSubmenu() { PMenu sub = new PMenu("Noise"); sub.buildFA(JHReduceNoise.NAME, JHReduceNoise::new).noGUI().add(); sub.buildFA(JHMedian.NAME, JHMedian::new).noGUI().add(); sub.addSeparator(); sub.addFA(AddNoise.NAME, AddNoise::new); sub.addFA(JHPixelate.NAME, JHPixelate::new); return sub; } private static JMenu createRenderSubmenu() { PMenu sub = new PMenu("Render"); sub.addFA(Clouds.NAME, Clouds::new); sub.addFA(JHPlasma.NAME, JHPlasma::new); sub.addFA(ValueNoise.NAME, ValueNoise::new); sub.addSeparator(); sub.addFA(JHBrushedMetal.NAME, JHBrushedMetal::new); sub.addFA(JHCaustics.NAME, JHCaustics::new); sub.addFA(JHCells.NAME, JHCells::new); sub.addFA(FractalTree.NAME, FractalTree::new); sub.addFA(Marble.NAME, Marble::new); sub.addFA(Voronoi.NAME, Voronoi::new); sub.addFA(JHWood.NAME, JHWood::new); sub.addSeparator(); sub.add(createRenderShapesSubmenu()); return sub; } private static JMenu createRenderShapesSubmenu() { PMenu sub = new PMenu("Shapes"); sub.addFA("Flower of Life", FlowerOfLife::new); sub.addFA("Lissajous Curve", Lissajous::new); sub.addFA("Mystic Rose", MysticRose::new); sub.addFA("Spirograph", Spirograph::new); return sub; } private static JMenu createArtisticSubmenu() { PMenu sub = new PMenu("Artistic"); sub.addFA(JHCrystallize.NAME, JHCrystallize::new); sub.addFA(JHEmboss.NAME, JHEmboss::new); sub.addFA(JHOilPainting.NAME, JHOilPainting::new); sub.addFA(Orton.NAME, Orton::new); sub.addFA(PhotoCollage.NAME, PhotoCollage::new); sub.addFA(JHPointillize.NAME, JHPointillize::new); sub.addFA(RandomSpheres.NAME, RandomSpheres::new); sub.addFA(JHSmear.NAME, JHSmear::new); sub.addFA(JHStamp.NAME, JHStamp::new); sub.addFA(JHWeave.NAME, JHWeave::new); return sub; } private static JMenu createFindEdgesSubmenu() { PMenu sub = new PMenu("Find Edges"); sub.addFA(JHConvolutionEdge.NAME, JHConvolutionEdge::new); sub.addAction(new FilterAction(JHLaplacian.NAME, JHLaplacian::new) .withoutGUI()); sub.addFA(JHDifferenceOfGaussians.NAME, JHDifferenceOfGaussians::new); sub.addFA("Canny", Canny::new); return sub; } private static JMenu createOtherSubmenu() { PMenu sub = new PMenu("Other"); sub.addFA(JHDropShadow.NAME, JHDropShadow::new); sub.addFA(Morphology.NAME, Morphology::new); sub.addFA("Random Filter", RandomFilter::new); sub.addFA("Transform Layer", TransformLayer::new); sub.addFA(Transition2D.NAME, Transition2D::new); sub.addSeparator(); sub.addFA(Convolve.createFilterAction(3)); sub.addFA(Convolve.createFilterAction(5)); sub.addSeparator(); sub.addFA(ChannelToTransparency.NAME, ChannelToTransparency::new); sub.buildFA(InvertTransparency.NAME, InvertTransparency::new).noGUI().add(); return sub; } private static JMenu createViewMenu(PixelitorWindow pw) { PMenu viewMenu = new PMenu("View", 'V'); viewMenu.add(ZoomMenu.INSTANCE); viewMenu.addSeparator(); viewMenu.addAction(new MenuAction("Show History...") { @Override public void onClick() { History.showHistory(); } }); viewMenu.addAction(new MenuAction("Show Navigator...") { @Override public void onClick() { Navigator.showInDialog(pw); } }); viewMenu.addSeparator(); viewMenu.add(createColorVariationsSubmenu(pw)); viewMenu.addAlwaysEnabledAction(new MenuAction("Color Palette...") { @Override public void onClick() { FullPalette palette = new FullPalette(); VariationsPanel.showDialog(pw, palette, ColorSwatchClickHandler.STANDARD); } }); viewMenu.addSeparator(); viewMenu.add(new ShowHideStatusBarAction()); viewMenu.buildAction(new ShowHideHistogramsAction()).alwaysEnabled().withKey(F6).add(); viewMenu.buildAction(new ShowHideLayersAction()).alwaysEnabled().withKey(F7).add(); viewMenu.add(new ShowHideToolsAction()); viewMenu.buildAction(ShowHideAllAction.INSTANCE).alwaysEnabled().withKey(TAB).add(); viewMenu.addAlwaysEnabledAction(new MenuAction("Set Default Workspace") { @Override public void onClick() { AppPreferences.WorkSpace.setDefault(pw); } }); JCheckBoxMenuItem showPixelGridMI = new JCheckBoxMenuItem("Show Pixel Grid"); showPixelGridMI.addActionListener(e -> { ImageComponent.showPixelGrid = showPixelGridMI.getState(); ImageComponents.repaintAll(); }); viewMenu.add(showPixelGridMI); viewMenu.addSeparator(); viewMenu.add(createArrangeWindowsSubmenu()); return viewMenu; } private static JMenu createColorVariationsSubmenu(PixelitorWindow pw) { PMenu variations = new PMenu("Color Variations"); variations.addAlwaysEnabledAction(new MenuAction("Foreground...") { @Override public void onClick() { VariationsPanel.showFGVariationsDialog(pw); } }); variations.addAlwaysEnabledAction(new MenuAction("HSB Mix Foreground with Background...") { @Override public void onClick() { VariationsPanel.showHSBMixDialog(pw, true); } }); variations.addAlwaysEnabledAction(new MenuAction("RGB Mix Foreground with Background...") { @Override public void onClick() { VariationsPanel.showRGBMixDialog(pw, true); } }); variations.addSeparator(); variations.addAlwaysEnabledAction(new MenuAction("Background...") { @Override public void onClick() { VariationsPanel.showBGVariationsDialog(pw); } }); variations.addAlwaysEnabledAction(new MenuAction("HSB Mix Background with Foreground...") { @Override public void onClick() { VariationsPanel.showHSBMixDialog(pw, false); } }); variations.addAlwaysEnabledAction(new MenuAction("RGB Mix Background with Foreground...") { @Override public void onClick() { VariationsPanel.showRGBMixDialog(pw, false); } }); return variations; } private static JMenu createArrangeWindowsSubmenu() { PMenu sub = new PMenu("Arrange Windows"); sub.addAction(new MenuAction("Cascade") { @Override public void onClick() { Desktop.INSTANCE.cascadeWindows(); } }); sub.addAction(new MenuAction("Tile") { @Override public void onClick() { Desktop.INSTANCE.tileWindows(); } }); return sub; } private static JMenu createDevelopMenu(PixelitorWindow pw) { PMenu developMenu = new PMenu("Develop", 'D'); developMenu.add(createDebugSubmenu(pw)); developMenu.add(createTestSubmenu(pw)); developMenu.add(createSplashSubmenu()); developMenu.add(createExperimentalSubmenu()); developMenu.addAlwaysEnabledAction(new MenuAction("Filter Creator...") { @Override public void onClick() { FilterCreator.showInDialog(pw); } }); developMenu.addAlwaysEnabledAction(new MenuAction("Debug Special") { @Override public void onClick() { BufferedImage img = ImageComponents.getActiveCompositeImage(); Composite composite = new MultiplyComposite(1.0f); long startTime = System.nanoTime(); int testsRun = 100; for (int i = 0; i < testsRun; i++) { Graphics2D g = img.createGraphics(); g.setComposite(composite); g.drawImage(img, 0, 0, null); g.dispose(); } long totalTime = (System.nanoTime() - startTime) / 1000000; System.out.println("MenuBar.actionPerformed: it took " + totalTime + " ms, average time = " + totalTime / testsRun); } }); developMenu.addAction(new MenuAction("Dump Event Queue") { @Override public void onClick() { Events.dumpAll(); } }); developMenu.addAction(new MenuAction("Debug PID") { @Override public void onClick() { String vmRuntimeInfo = ManagementFactory.getRuntimeMXBean().getName(); System.out.println(String.format("MenuBar::onClick: vmRuntimeInfo = '%s'", vmRuntimeInfo)); } }); developMenu.addAction(new MenuAction("Debug Layer Mask") { @Override public void onClick() { ImageLayer imageLayer = (ImageLayer) getActiveLayerOrNull(); Utils.debugImage(imageLayer.getImage(), "layer image"); if (imageLayer.hasMask()) { LayerMask layerMask = imageLayer.getMask(); BufferedImage maskImage = layerMask.getImage(); Utils.debugImage(maskImage, "mask image"); BufferedImage transparencyImage = layerMask.getTransparencyImage(); Utils.debugImage(transparencyImage, "transparency image"); } } }); developMenu.addAction(new MenuAction("Mask update transparency from BW") { @Override public void onClick() { ImageLayer imageLayer = (ImageLayer) getActiveLayerOrNull(); if (imageLayer.hasMask()) { imageLayer.getMask().updateFromBWImage(); imageLayer.getComp().imageChanged(FULL); } else { Messages.showInfo("No Mask in Current image", "Error"); } } }); developMenu.addAction(new MenuAction("imageChanged(FULL) on the active image") { @Override public void onClick() { getActiveCompOrNull().imageChanged(FULL, true); } }); developMenu.addAction(new MenuAction("Debug getCanvasSizedSubImage") { @Override public void onClick() { ImageComponents.onActiveDrawable( dr -> Utils.debugImage(dr.getCanvasSizedSubImage())); } }); developMenu.addAction(new MenuAction("Tests3x3.dumpCompositeOfActive()") { @Override public void onClick() { Tests3x3.dumpCompositeOfActive(); } }); developMenu.addAction(new MenuAction("Debug translation and canvas") { @Override public void onClick() { ImageComponents.onActiveImageLayer(layer -> { String s = layer.toDebugCanvasString(); System.out.println(s); }); } }); developMenu.addAction(new MenuAction("Debug Copy Brush") { @Override public void onClick() { CopyBrush.setDebugBrushImage(true); } }); developMenu.addAction(new MenuAction("Create All Filters") { @Override public void onClick() { FilterUtils.createAllFilters(); } }); developMenu.addAction(new MenuAction("debug FgBgColorSelector") { @Override public void onClick() { FgBgColors.getGUI().dumpState(); } }); return developMenu; } private static JMenu createDebugSubmenu(PixelitorWindow pw) { PMenu sub = new PMenu("Debug"); sub.addAction(new MenuAction("repaint() on the active image") { @Override public void onClick() { ImageComponents.repaintActive(); } }); sub.addAlwaysEnabledAction(new MenuAction("revalidate() the main window") { @Override public void onClick() { pw.getContentPane().revalidate(); } }); sub.addAction(new MenuAction("reset the translation of current layer") { @Override public void onClick() { Composition comp = getActiveCompOrNull(); Layer layer = comp.getActiveLayer(); if (layer instanceof ContentLayer) { ContentLayer contentLayer = (ContentLayer) layer; contentLayer.setTranslation(0, 0); } if (layer.hasMask()) { layer.getMask().setTranslation(0, 0); } comp.imageChanged(FULL); } }); sub.addAction(new MenuAction("Update Histograms") { @Override public void onClick() { Composition comp = getActiveCompOrNull(); HistogramsPanel.INSTANCE.updateFromCompIfShown(comp); } }); sub.addAction(new MenuAction("Save All Images to Folder...") { @Override public void onClick() { OpenSaveManager.saveAllImagesToDir(); } }); sub.addAction(new MenuAction("Debug ImageLayer Images") { @Override public void onClick() { ImageComponents.onActiveDrawable( Drawable::debugImages); } }); return sub; } private static JMenu createTestSubmenu(PixelitorWindow pw) { PMenu sub = new PMenu("Test"); sub.addFA("ParamTest", ParamTest::new); sub.addActionWithKey(new MenuAction("Random Resize") { @Override public void onClick() { FilterTests.randomResize(); } }, CTRL_ALT_R); sub.buildAction(new MenuAction("Random GUI Test") { @Override public void onClick() { RandomGUITest.runTest(); } }).alwaysEnabled().withKey(CTRL_R).add(); sub.addAction(new ImageLayerAction("Filter Performance Test") { @Override protected void process(ImageLayer layer) { new PerformanceTestingDialog(pw, layer); } }); sub.addAction(new ImageLayerAction("Find Slowest Filter", false) { @Override protected void process(ImageLayer layer) { FilterTests.findSlowestFilter(layer); } }); sub.addAction(new MenuAction("getCompositeImage() Performance Test...") { @Override public void onClick() { FilterTests.getCompositeImagePerformanceTest(); } }); sub.addSeparator(); sub.addAction(new ImageLayerAction("Run All Filters on Current Layer/Mask", false) { @Override protected void process(ImageLayer layer) { FilterTests.runAllFiltersOn(layer); } }); sub.addAction(new ImageLayerAction("Save the Result of Each Filter...") { @Override protected void process(ImageLayer layer) { FilterTests.saveTheResultOfEachFilter(layer); } }); sub.addAction(new MenuAction("Save Current Image in All Formats...") { @Override public void onClick() { OpenSaveManager.saveCurrentImageInAllFormats(); } }); sub.addSeparator(); sub.addAlwaysEnabledAction(new ImageLayerAction("Test Tools", false) { @Override protected void process(ImageLayer layer) { ToolTests.testTools(layer); } }); return sub; } private static JMenu createSplashSubmenu() { PMenu sub = new PMenu("Splash"); sub.addAlwaysEnabledAction(new MenuAction("Create Splash Image") { @Override public void onClick() { SplashImageCreator.createSplashImage(); } }); sub.addAlwaysEnabledAction(new MenuAction("Save Many Splash Images...") { @Override public void onClick() { SplashImageCreator.saveManySplashImages(); } }); return sub; } private static JMenu createExperimentalSubmenu() { PMenu sub = new PMenu("Experimental"); sub.addFA(Contours.NAME, Contours::new); sub.addSeparator(); sub.addFA(Droste.NAME, Droste::new); sub.addFA(Sphere3D.NAME, Sphere3D::new); sub.addFA("Grid", RenderGrid::new); sub.addFA(EmptyPolar.NAME, EmptyPolar::new); sub.addFA(JHCheckerFilter.NAME, JHCheckerFilter::new); return sub; } private static JMenu createHelpMenu(PixelitorWindow pw) { PMenu helpMenu = new PMenu("Help", 'H'); helpMenu.addAlwaysEnabledAction(new MenuAction("Tip of the Day") { @Override public void onClick() { TipsOfTheDay.showTips(pw, true); } }); // JMenu sub = new JMenu("Web"); // MenuFactory.createMenuItem(new OpenInBrowserAction("Ask for Help", "https://sourceforge.net/projects/pixelitor/forums/forum/1034234"), null, sub, MenuEnableCondition.ACTION_ENABLED); // MenuFactory.createMenuItem(new OpenInBrowserAction("Discuss Pixelitor", "https://sourceforge.net/projects/pixelitor/forums/forum/1034233"), null, sub, MenuEnableCondition.ACTION_ENABLED); // MenuFactory.createMenuItem(new OpenInBrowserAction("Report a Bug", "https://sourceforge.net/tracker/?group_id=285935&atid=1211793"), null, sub, MenuEnableCondition.ACTION_ENABLED); // helpMenu.add(sub); helpMenu.addSeparator(); helpMenu.add(new MenuAction("Internal State...") { @Override public void onClick() { AppNode node = new AppNode(); String title = "Internal State"; JTree tree = new JTree(node); JLabel explainLabel = new JLabel("<html>If you are reporting a bug that cannot be reproduced," + "<br>please include the following information:"); explainLabel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); JPanel form = new JPanel(new BorderLayout()); form.add(explainLabel, BorderLayout.NORTH); form.add(new JScrollPane(tree), BorderLayout.CENTER); String text = node.toDetailedString(); GUIUtils.showClipboardTextDialog(form, title, text); } }); helpMenu.add(new MenuAction("Check for Update...") { @Override public void onClick() { UpdatesCheck.checkForUpdates(); } }); helpMenu.addAlwaysEnabledAction(new MenuAction("About") { @Override public void onClick() { AboutDialog.showDialog(pw); } }); return helpMenu; } }