/************************************************************************** OmegaT - Computer Assisted Translation (CAT) tool with fuzzy matching, translation memory, keyword search, glossaries, and translation leveraging into updated projects. Copyright (C) 2015 Aaron Madlon-Kay Home page: http://www.omegat.org/ Support center: http://groups.yahoo.com/group/OmegaT/ This file is part of OmegaT. OmegaT is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. OmegaT 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 this program. If not, see <http://www.gnu.org/licenses/>. **************************************************************************/ package org.omegat.util.gui; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Font; import java.awt.Rectangle; import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.Transferable; import java.awt.datatransfer.UnsupportedFlavorException; import java.awt.dnd.DnDConstants; import java.awt.dnd.DropTarget; import java.awt.dnd.DropTargetAdapter; import java.awt.dnd.DropTargetDragEvent; import java.awt.dnd.DropTargetDropEvent; import java.awt.dnd.DropTargetEvent; import java.awt.dnd.DropTargetListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.TooManyListenersException; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JLayeredPane; import javax.swing.JMenuBar; import javax.swing.JPanel; import javax.swing.JRootPane; import javax.swing.SwingConstants; import javax.swing.SwingUtilities; import javax.swing.border.EmptyBorder; import org.omegat.gui.main.ProjectUICommands; import org.omegat.util.Log; /** * @author Aaron Madlon-Kay */ public class DragTargetOverlay { private static int MARGIN = 0; public static void apply(final JComponent comp, final IDropInfo info) { DropTargetListener listener = new DropTargetAdapter() { private JPanel panel = null; @Override public void dragEnter(DropTargetDragEvent dtde) { if (!dtde.isDataFlavorSupported(info.getDataFlavor()) || !info.canAcceptDrop()) { return; } final JLayeredPane layeredPane = SwingUtilities.getRootPane(comp).getLayeredPane(); if (panel == null) { panel = createOverlayPanel(comp, layeredPane, info); } layeredPane.add(panel, JLayeredPane.MODAL_LAYER); Rectangle rect = calculateBounds(info.getComponentToOverlay()); panel.setBounds(rect); panel.doLayout(); layeredPane.repaint(); // Repaint again later because the panel might paint itself again if it wraps. SwingUtilities.invokeLater(new Runnable() { @Override public void run() { layeredPane.repaint(); } }); } private Rectangle calculateBounds(Component overlayComponent) { JRootPane rootPane = SwingUtilities.getRootPane(overlayComponent); Rectangle rect = SwingUtilities.convertRectangle(overlayComponent.getParent(), overlayComponent.getBounds(), rootPane.getContentPane()); JMenuBar menuBar = rootPane.getJMenuBar(); rect.x += MARGIN; rect.y += MARGIN + (menuBar == null ? 0 : menuBar.getHeight()); rect.width -= MARGIN * 2; rect.height -= MARGIN * 2; return rect; } @Override public void drop(DropTargetDropEvent dtde) {} }; addListener(comp, listener); } private static JPanel createOverlayPanel(final JComponent comp, final JLayeredPane layeredPane, final IDropInfo info) { final JPanel panel = new JPanel(); panel.setLayout(new BorderLayout()); JLabel label = new JLabel("<html><center>" + info.getOverlayMessage() + "</center></html>"); label.setForeground(Color.WHITE); label.setHorizontalAlignment(SwingConstants.CENTER); label.setFont(label.getFont().deriveFont(Font.BOLD)); label.setBorder(new EmptyBorder(20, 20, 20, 20)); panel.add(label, BorderLayout.CENTER); panel.setBackground(new Color(0, 0, 0, 150)); addListener(panel, new FileDropListener(info) { @Override public void drop(DropTargetDropEvent dtde) { super.drop(dtde); restore(); } @Override public void dragExit(DropTargetEvent dte) { restore(); } private void restore() { layeredPane.remove(panel); layeredPane.repaint(); } }); // This listener shouldn't be necessary(?), but sometimes dragExit() isn't called // properly and the overlay can get stuck even though the drag has ended. // This lets you kill the overlay by moving the mouse in and out of it. panel.addMouseListener(new MouseAdapter() { @Override public void mouseExited(MouseEvent e) { layeredPane.remove(panel); layeredPane.repaint(); } }); return panel; } private static class FileDropListener extends DropTargetAdapter { private final IDropInfo info; public FileDropListener(IDropInfo info) { this.info = info; } private void verifyDrag(DropTargetDragEvent dtde) { if (dtde.isDataFlavorSupported(info.getDataFlavor()) && (dtde.getSourceActions() & info.getDnDAction()) != 0) { dtde.acceptDrag(info.getDnDAction()); } else { dtde.rejectDrag(); } } @Override public void dragEnter(DropTargetDragEvent dtde) { verifyDrag(dtde); } @Override public void dragOver(DropTargetDragEvent dtde) { verifyDrag(dtde); } @Override public void dropActionChanged(DropTargetDragEvent dtde) { verifyDrag(dtde); } @Override public void drop(DropTargetDropEvent dtde) { dtde.acceptDrop(info.getDnDAction()); Transferable transferable = dtde.getTransferable(); boolean success = false; try { Object result = transferable.getTransferData(info.getDataFlavor()); success = info.handleDroppedObject(result); } catch (UnsupportedFlavorException e) { Log.log(e); } catch (IOException e) { Log.log(e); } dtde.dropComplete(success); } } private static void addListener(JComponent comp, DropTargetListener listener) { DropTarget target = comp.getDropTarget(); if (target == null) { comp.setDropTarget(new DropTarget(comp, listener)); } else { try { target.addDropTargetListener(listener); } catch (TooManyListenersException e) { Log.log(e); } } } public interface IDropInfo { public DataFlavor getDataFlavor(); public int getDnDAction(); public boolean canAcceptDrop(); public Component getComponentToOverlay(); public String getOverlayMessage(); public boolean handleDroppedObject(Object dropped); } public static abstract class FileDropInfo implements IDropInfo { private final boolean doReset; public FileDropInfo(boolean doReset) { this.doReset = doReset; } @Override public DataFlavor getDataFlavor() { return DataFlavor.javaFileListFlavor; } @Override public int getDnDAction() { return DnDConstants.ACTION_COPY; } private List<File> filterFiles(List<?> files) { List<File> filtered = new ArrayList<File>(files.size()); for (Object o : files) { File file = (File) o; if (file.exists() && file.canRead() && acceptFile(file)) { filtered.add(file); } } return filtered; } @Override public boolean handleDroppedObject(Object dropped) { return handleFiles(filterFiles((List<?>) dropped)); }; protected boolean handleFiles(final List<File> files) { if (files.isEmpty()) { return false; } // The import might take a long time if there are collision dialogs. // Invoke later so we can return successfully right away. SwingUtilities.invokeLater(new Runnable() { @Override public void run() { ProjectUICommands.projectImportFiles(getImportDestination(), files.toArray(new File[files.size()]), doReset); } }); return true; } protected abstract boolean acceptFile(File pathname); protected abstract String getImportDestination(); } }