/* * 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.gui; import pixelitor.AppLogic; import pixelitor.Build; import pixelitor.Composition; import pixelitor.filters.comp.Crop; import pixelitor.history.History; import pixelitor.history.PixelitorEdit; import pixelitor.io.OpenSaveManager; import pixelitor.layers.Drawable; import pixelitor.layers.ImageLayer; import pixelitor.layers.Layer; import pixelitor.layers.MaskViewMode; import pixelitor.layers.TextLayer; import pixelitor.menus.view.ZoomLevel; import pixelitor.menus.view.ZoomMenu; import pixelitor.selection.Selection; import pixelitor.selection.SelectionActions; import pixelitor.tools.Tools; import pixelitor.utils.ImageSwitchListener; import pixelitor.utils.Messages; import java.awt.Cursor; import java.awt.Rectangle; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.io.File; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Optional; import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.Function; /** * Static methods for maintaining the list of open ImageComponent objects */ public class ImageComponents { private static final List<ImageComponent> icList = new ArrayList<>(); private static ImageComponent activeIC; private static final Collection<ImageSwitchListener> imageSwitchListeners = new ArrayList<>(); private ImageComponents() { } public static void addIC(ImageComponent ic) { icList.add(ic); } public static boolean thereAreUnsavedChanges() { for (ImageComponent ic : icList) { if (ic.getComp().isDirty()) { return true; } } return false; } public static List<ImageComponent> getICList() { return icList; } private static void setNewImageAsActiveIfNecessary() { if (!icList.isEmpty()) { boolean activeFound = false; for (ImageComponent ic : icList) { if (ic == activeIC) { activeFound = true; break; } } if (!activeFound) { setActiveIC(icList.get(0), true); } } } public static ImageComponent getActiveIC() { return activeIC; } public static boolean hasActiveImage() { return activeIC != null && !icList.isEmpty(); } public static Composition getActiveCompOrNull() { if (activeIC != null) { return activeIC.getComp(); } // there is no open image return null; } public static Optional<Composition> getActiveComp() { if (activeIC != null) { return Optional.of(activeIC.getComp()); } // there is no open image return Optional.empty(); } public static Optional<Composition> findCompositionByName(String name) { return icList.stream() .map(ImageComponent::getComp) .filter(c -> c.getName().equals(name)) .findFirst(); } public static Layer getActiveLayerOrNull() { if (activeIC != null) { return activeIC.getComp().getActiveLayer(); } return null; } public static Optional<Layer> getActiveLayer() { return getActiveComp().map(Composition::getActiveLayer); } public static Drawable getActiveDrawableOrNull() { if (activeIC != null) { Composition comp = activeIC.getComp(); return comp.getActiveDrawableOrNull(); } return null; } public static int getNumOpenImages() { return icList.size(); } public static BufferedImage getActiveCompositeImage() { if (activeIC != null) { return activeIC.getComp().getCompositeImage(); } return null; } /** * Crops the active image based on the crop tool */ public static void toolCropActiveImage(boolean allowGrowing) { try { onActiveComp(comp -> { Rectangle2D cropRect = Tools.CROP.getCropRect(comp.getIC()); new Crop(cropRect, false, allowGrowing).process(comp); }); } catch (Exception ex) { Messages.showException(ex); } } /** * Crops the active image based on the selection bounds */ public static void selectionCropActiveImage() { try { Composition comp = getActiveCompOrNull(); if (comp != null) { comp.onSelection(selection -> { Rectangle selectionBounds = selection.getShapeBounds(); new Crop(selectionBounds, true, true).process(comp); }); } } catch (Exception ex) { Messages.showException(ex); } } public static void imageClosed(ImageComponent ic) { icList.remove(ic); if (icList.isEmpty()) { onAllImagesClosed(); } setNewImageAsActiveIfNecessary(); } public static void setActiveIC(ImageComponent ic, boolean activate) { activeIC = ic; if (activate) { if (ic == null) { throw new IllegalStateException("cannot activate null imageComponent"); } // activate is always false in unit tests ImageFrame frame = ic.getFrame(); Desktop.INSTANCE.activateImageFrame(frame); ic.onActivation(); } } /** * When a new tool is activated, the cursor has to be changed for each image */ public static void setCursorForAll(Cursor cursor) { for (ImageComponent ic : icList) { ic.setCursor(cursor); } } public static void addImageSwitchListener(ImageSwitchListener listener) { imageSwitchListeners.add(listener); } public static void removeImageSwitchListener(ImageSwitchListener listener) { imageSwitchListeners.remove(listener); } private static void onAllImagesClosed() { setActiveIC(null, false); imageSwitchListeners.forEach(ImageSwitchListener::noOpenImageAnymore); History.onAllImagesClosed(); SelectionActions.setEnabled(false, null); PixelitorWindow.getInstance().setTitle(Build.getPixelitorWindowFixTitle()); } /** * Another image became active */ public static void activeImageHasChanged(ImageComponent ic) { ImageComponent oldIC = activeIC; setActiveIC(ic, false); for (ImageSwitchListener listener : imageSwitchListeners) { listener.activeImageHasChanged(oldIC, ic); } Composition newActiveComp = ic.getComp(); Layer layer = newActiveComp.getActiveLayer(); AppLogic.activeLayerChanged(layer); SelectionActions.setEnabled(newActiveComp.hasSelection(), newActiveComp); ZoomMenu.zoomChanged(ic.getZoomLevel()); AppLogic.activeCompSizeChanged(newActiveComp); PixelitorWindow.getInstance().setTitle(ic.getComp().getName() + " - " + Build.getPixelitorWindowFixTitle()); } public static void newImageOpened(Composition comp) { imageSwitchListeners.forEach((imageSwitchListener) -> imageSwitchListener.newImageOpened(comp)); } public static void repaintActive() { if (activeIC != null) { activeIC.repaint(); } } public static void repaintAll() { //noinspection Convert2streamapi for (ImageComponent ic : icList) { ic.repaint(); } } public static void fitActiveToScreen() { if (activeIC != null) { activeIC.zoomToFitScreen(); } } public static void fitActiveToActualPixels() { if (activeIC != null) { activeIC.setZoomAtCenter(ZoomLevel.Z100); } } public static boolean isActive(ImageComponent ic) { return ic == activeIC; } public static void reloadActiveFromFile() { Composition comp = activeIC.getComp(); File file = comp.getFile(); if (file == null) { String msg = String.format("The image '%s' cannot be reloaded because it was not yet saved.", comp.getName()); Messages.showError("No file", msg); return; } if (!file.exists()) { String msg = String.format("The image '%s' cannot be reloaded because the file\n" + "%s\n" + "does not exist anymore.", comp.getName(), file.getAbsolutePath()); Messages.showError("No file found", msg); return; } Composition newComp = OpenSaveManager.createCompositionFromFile(file); PixelitorEdit edit = activeIC.replaceComp(newComp, true, MaskViewMode.NORMAL); // needs to be called before addEdit because of the consistency checks newImageOpened(newComp); assert edit != null; History.addEdit(edit); String msg = String.format("The image '%s' was reloaded from the file %s.", comp.getName(), file.getAbsolutePath()); Messages.showStatusMessage(msg); } public static void duplicateActive() { assert activeIC != null; Composition newComp = Composition.createCopy(activeIC.getComp(), false); AppLogic.addCompAsNewImage(newComp); } public static void onActiveIC(Consumer<ImageComponent> action) { if (activeIC != null) { action.accept(activeIC); } } public static void onActiveICAndComp(BiConsumer<ImageComponent, Composition> action) { if (activeIC != null) { Composition comp = activeIC.getComp(); action.accept(activeIC, comp); } } public static <T> T fromActiveIC(Function<ImageComponent, T> function) { return function.apply(activeIC); } public static void forAllImages(Consumer<ImageComponent> action) { //noinspection Convert2streamapi for (ImageComponent ic : icList) { action.accept(ic); } } public static void onActiveComp(Consumer<Composition> action) { if (activeIC != null) { Composition comp = activeIC.getComp(); action.accept(comp); } } public static void onActiveSelection(Consumer<Selection> action) { if (activeIC != null) { activeIC.getComp().onSelection(action); } } public static void onActiveLayer(Consumer<Layer> action) { if (activeIC != null) { Layer activeLayer = activeIC.getComp().getActiveLayer(); action.accept(activeLayer); } } public static void onActiveImageLayer(Consumer<ImageLayer> action) { if (activeIC != null) { ImageLayer activeLayer = (ImageLayer) activeIC.getComp().getActiveLayer(); action.accept(activeLayer); } } public static void onActiveTextLayer(Consumer<TextLayer> action) { if (activeIC != null) { TextLayer activeLayer = (TextLayer) activeIC.getComp().getActiveLayer(); action.accept(activeLayer); } } public static void onActiveDrawable(Consumer<Drawable> action) { Drawable dr = getActiveDrawableOrNull(); if (dr != null) { action.accept(dr); } } }