├── .travis.yml
├── src
└── main
│ ├── resources
│ ├── tinylog.properties
│ └── icons
│ │ ├── pdfjumbler.ico
│ │ ├── document-save.svg
│ │ ├── menu.svg
│ │ ├── document-open.svg
│ │ ├── zoom-out.svg
│ │ ├── zoom-in.svg
│ │ ├── Makefile
│ │ ├── rotate-ccw.svg
│ │ ├── rotate-cw.svg
│ │ ├── edit-delete.svg
│ │ └── pdfjumbler.svg
│ └── java
│ └── net
│ └── sourceforge
│ └── pdfjumbler
│ ├── ConfigKeys.java
│ ├── pdf
│ ├── PdfProcessorListener.java
│ ├── PluginException.java
│ ├── PdfEditor.java
│ ├── PdfRenderer.java
│ ├── Page.java
│ ├── Plugin.java
│ └── PdfProcessingFactory.java
│ ├── i18n
│ ├── I18nUtil.java
│ ├── I18nKeys.java
│ ├── PdfJumblerResources_de.java
│ ├── PdfJumblerResources_ru.java
│ ├── PdfJumblerResources_es.java
│ └── PdfJumblerResources.java
│ ├── UniqueUndoManager.java
│ ├── actions
│ ├── RotateClockwiseAction.java
│ ├── RotateCounterClockwiseAction.java
│ ├── RedoAction.java
│ ├── ChangeEditorAction.java
│ ├── ZoomInAction.java
│ ├── ReloadingProcessorListener.java
│ ├── ZoomOutAction.java
│ ├── ChangeRendererAction.java
│ ├── UndoAction.java
│ ├── ViewThumbnailsAction.java
│ ├── ViewListAction.java
│ ├── AboutAction.java
│ ├── SaveDocumentWorker.java
│ ├── ClearAction.java
│ ├── MoveUpAction.java
│ ├── MoveDownAction.java
│ ├── DelAction.java
│ ├── OpenDocumentWorker.java
│ ├── DocOpenAction.java
│ ├── RotateAction.java
│ ├── DocSaveAction.java
│ └── MenuAction.java
│ ├── jdragdroplist
│ ├── DropListener.java
│ ├── JDDLTransferData.java
│ ├── ListEdit.java
│ ├── UndoableListModel.java
│ ├── DropUtil.java
│ ├── JDragDropList.java
│ └── UndoableList.java
│ ├── DocumentManager.java
│ ├── pdfbox
│ ├── PdfEditor.java
│ └── PdfRenderer.java
│ ├── TrashDropTargetListener.java
│ ├── Actions.java
│ ├── Icons.java
│ ├── PdfCellRenderer.java
│ ├── ProgressDialog.java
│ ├── PdfList.java
│ └── PdfJumbler.java
├── pdfjumbler.nsi
├── screenshots
└── screenshot01.png
├── .gitignore
├── setup
└── ubuntu
│ └── install.sh
├── README.md
└── LICENSE
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: java
2 | jdk:
3 | - openjdk11
4 |
--------------------------------------------------------------------------------
/src/main/resources/tinylog.properties:
--------------------------------------------------------------------------------
1 | level = trace
2 |
--------------------------------------------------------------------------------
/pdfjumbler.nsi:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mgropp/pdfjumbler/HEAD/pdfjumbler.nsi
--------------------------------------------------------------------------------
/screenshots/screenshot01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mgropp/pdfjumbler/HEAD/screenshots/screenshot01.png
--------------------------------------------------------------------------------
/src/main/resources/icons/pdfjumbler.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mgropp/pdfjumbler/HEAD/src/main/resources/icons/pdfjumbler.ico
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.class
2 |
3 | # Mobile Tools for Java (J2ME)
4 | .mtj.tmp/
5 |
6 | # Package Files #
7 | *.jar
8 | *.war
9 | *.ear
10 |
11 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
12 | hs_err_pid*
13 |
--------------------------------------------------------------------------------
/src/main/java/net/sourceforge/pdfjumbler/ConfigKeys.java:
--------------------------------------------------------------------------------
1 | package net.sourceforge.pdfjumbler;
2 |
3 | public class ConfigKeys {
4 | public static final String EDITOR = "editor";
5 | public static final String RENDERER = "renderer";
6 | public static final String SHOW_TEXT = "showText";
7 | }
8 |
--------------------------------------------------------------------------------
/src/main/resources/icons/document-save.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/icons/menu.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/icons/document-open.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/java/net/sourceforge/pdfjumbler/pdf/PdfProcessorListener.java:
--------------------------------------------------------------------------------
1 | package net.sourceforge.pdfjumbler.pdf;
2 |
3 | /**
4 | * @author Martin Gropp
5 | */
6 | public interface PdfProcessorListener {
7 | void pdfEditorChanged(PdfEditor oldEditor, PdfEditor newEditor);
8 | void pdfRendererChanged(PdfRenderer oldRenderer, PdfRenderer newRenderer);
9 | }
10 |
--------------------------------------------------------------------------------
/src/main/java/net/sourceforge/pdfjumbler/i18n/I18nUtil.java:
--------------------------------------------------------------------------------
1 | package net.sourceforge.pdfjumbler.i18n;
2 |
3 | import java.awt.Toolkit;
4 | import java.util.ResourceBundle;
5 |
6 | public class I18nUtil {
7 | static final int MENU_SHORTCUT_KEY = Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx();
8 |
9 | public static String getString(ResourceBundle bundle, String key, String fallback) {
10 | return bundle.containsKey(key) ? bundle.getString(key) : fallback;
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/main/java/net/sourceforge/pdfjumbler/pdf/PluginException.java:
--------------------------------------------------------------------------------
1 | package net.sourceforge.pdfjumbler.pdf;
2 |
3 | /**
4 | * @author Martin Gropp
5 | */
6 | public class PluginException extends Exception {
7 | private static final long serialVersionUID = 1L;
8 |
9 | public PluginException(String msg) {
10 | super(msg);
11 | }
12 |
13 | public PluginException(Throwable cause) {
14 | super(cause);
15 | }
16 |
17 | public PluginException(String msg, Throwable cause) {
18 | super(msg, cause);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/resources/icons/zoom-out.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/java/net/sourceforge/pdfjumbler/pdf/PdfEditor.java:
--------------------------------------------------------------------------------
1 | package net.sourceforge.pdfjumbler.pdf;
2 |
3 | import java.io.File;
4 | import java.io.IOException;
5 | import java.util.List;
6 |
7 | /**
8 | * @author Martin Gropp
9 | */
10 | public interface PdfEditor {
11 | /**
12 | * Save pages to a PDF file.
13 | *
14 | * @param pages
15 | * @param file
16 | * @throws IOException
17 | */
18 | void saveDocument(List pages, File file) throws IOException;
19 |
20 | /**
21 | * Close the editor, for example because a different editor is selected.
22 | */
23 | void close() throws IOException;
24 | }
25 |
--------------------------------------------------------------------------------
/src/main/resources/icons/zoom-in.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/icons/Makefile:
--------------------------------------------------------------------------------
1 | # Requires ImageMagick
2 | pdfjumbler.ico: pdfjumbler-tiny.svg pdfjumbler.svg
3 | dir=`mktemp -d` && \
4 | convert -resize 16x16 -extent 16x16 -gravity center -antialias pdfjumbler-tiny.svg $${dir}/pdfjumbler-16.png && \
5 | convert -resize 24x24 -extent 24x24 -gravity center -antialias pdfjumbler-tiny.svg $${dir}/pdfjumbler-24.png && \
6 | convert -resize 32x32 -extent 32x32 -gravity center -antialias pdfjumbler.svg $${dir}/pdfjumbler-32.png && \
7 | convert -resize 48x48 -extent 48x48 -gravity center -antialias pdfjumbler.svg $${dir}/pdfjumbler-48.png && \
8 | convert -resize 64x64 -extent 64x64 -gravity center -antialias pdfjumbler.svg $${dir}/pdfjumbler-64.png && \
9 | convert $${dir}/*.png ./pdfjumbler.ico && \
10 | rm -rf $${dir}
11 |
--------------------------------------------------------------------------------
/src/main/java/net/sourceforge/pdfjumbler/UniqueUndoManager.java:
--------------------------------------------------------------------------------
1 | package net.sourceforge.pdfjumbler;
2 |
3 | import javax.swing.undo.UndoManager;
4 | import javax.swing.undo.UndoableEdit;
5 |
6 | /**
7 | * A special UndoManager that adds edits only if the
8 | * last edit is not the same as the new one.
9 | * This is needed for move operations between lists
10 | * (implemented as remove + add compound edits) and
11 | * is simpler than using insignificant edits.
12 | *
13 | * @author Martin Gropp
14 | */
15 | class UniqueUndoManager extends UndoManager {
16 | private static final long serialVersionUID = -2740016241678747836L;
17 |
18 | @Override
19 | public boolean addEdit(UndoableEdit edit) {
20 | if ((lastEdit() != null) && lastEdit().equals(edit)) {
21 | return false;
22 | } else {
23 | return super.addEdit(edit);
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/main/java/net/sourceforge/pdfjumbler/actions/RotateClockwiseAction.java:
--------------------------------------------------------------------------------
1 | package net.sourceforge.pdfjumbler.actions;
2 |
3 | import net.sourceforge.pdfjumbler.Icons;
4 | import net.sourceforge.pdfjumbler.PdfJumbler;
5 | import net.sourceforge.pdfjumbler.i18n.I18nKeys;
6 | import net.sourceforge.pdfjumbler.i18n.PdfJumblerResources;
7 |
8 | import javax.swing.*;
9 |
10 | public class RotateClockwiseAction extends RotateAction {
11 | private static final long serialVersionUID = 8258949195959150573L;
12 |
13 | public RotateClockwiseAction(PdfJumbler parent) {
14 | super(PdfJumblerResources.getResources().getString(I18nKeys.ROTATE_CW), Icons.ROTATE_CW, parent, 90);
15 | putValue(Action.SHORT_DESCRIPTION, PdfJumblerResources.getResources().getString(I18nKeys.ROTATE_CW));
16 | putValue(Action.ACCELERATOR_KEY, PdfJumblerResources.getResources().getObject(I18nKeys.ACCELERATOR_ROTATE_CW));
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/main/java/net/sourceforge/pdfjumbler/jdragdroplist/DropListener.java:
--------------------------------------------------------------------------------
1 | package net.sourceforge.pdfjumbler.jdragdroplist;
2 |
3 | import javax.swing.TransferHandler;
4 |
5 | /**
6 | * @author Martin Gropp
7 | */
8 | public interface DropListener {
9 | /**
10 | * Called for all possible drops, including list items.
11 | * Return false to reject a drop.
12 | *
13 | * @param sender
14 | * The JDragDropList.
15 | * @param info
16 | * @return
17 | */
18 | boolean acceptDrop(Object sender, TransferHandler.TransferSupport info);
19 |
20 | /**
21 | * Called for drops the list can't handle itself (i.e. for everything
22 | * except other JDDL list items).
23 | * Return true iff the drop succeeded.
24 | *
25 | * @param sender
26 | * The JDragDropList.
27 | * @param info
28 | * @return
29 | */
30 | boolean handleDrop(Object sender, TransferHandler.TransferSupport info);
31 | }
32 |
--------------------------------------------------------------------------------
/src/main/java/net/sourceforge/pdfjumbler/actions/RotateCounterClockwiseAction.java:
--------------------------------------------------------------------------------
1 | package net.sourceforge.pdfjumbler.actions;
2 |
3 | import net.sourceforge.pdfjumbler.Icons;
4 | import net.sourceforge.pdfjumbler.PdfJumbler;
5 | import net.sourceforge.pdfjumbler.i18n.I18nKeys;
6 | import net.sourceforge.pdfjumbler.i18n.PdfJumblerResources;
7 |
8 | import javax.swing.*;
9 |
10 | public class RotateCounterClockwiseAction extends RotateAction {
11 | private static final long serialVersionUID = 8258949195959150573L;
12 |
13 | public RotateCounterClockwiseAction(PdfJumbler parent) {
14 | super(PdfJumblerResources.getResources().getString(I18nKeys.ROTATE_CCW), Icons.ROTATE_CCW, parent, 270);
15 | putValue(Action.SHORT_DESCRIPTION, PdfJumblerResources.getResources().getString(I18nKeys.ROTATE_CCW));
16 | putValue(Action.ACCELERATOR_KEY, PdfJumblerResources.getResources().getObject(I18nKeys.ACCELERATOR_ROTATE_CCW));
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/main/java/net/sourceforge/pdfjumbler/actions/RedoAction.java:
--------------------------------------------------------------------------------
1 | package net.sourceforge.pdfjumbler.actions;
2 |
3 | import net.sourceforge.pdfjumbler.i18n.I18nKeys;
4 | import net.sourceforge.pdfjumbler.i18n.PdfJumblerResources;
5 |
6 | import javax.swing.*;
7 | import javax.swing.undo.UndoManager;
8 | import java.awt.event.ActionEvent;
9 |
10 | public class RedoAction extends AbstractAction {
11 | private static final long serialVersionUID = -4360624396939210557L;
12 | private final UndoManager undoManager;
13 |
14 | public RedoAction(UndoManager undoManager) {
15 | super(PdfJumblerResources.getResources().getString(I18nKeys.REDO));
16 | this.undoManager = undoManager;
17 | putValue(Action.SHORT_DESCRIPTION, PdfJumblerResources.getResources().getString(I18nKeys.REDO));
18 | putValue(Action.ACCELERATOR_KEY, PdfJumblerResources.getResources().getObject(I18nKeys.ACCELERATOR_REDO));
19 | }
20 |
21 | @Override
22 | public void actionPerformed(ActionEvent e) {
23 | if (undoManager.canRedo()) {
24 | undoManager.redo();
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/main/java/net/sourceforge/pdfjumbler/actions/ChangeEditorAction.java:
--------------------------------------------------------------------------------
1 | package net.sourceforge.pdfjumbler.actions;
2 |
3 | import net.sourceforge.pdfjumbler.PdfJumbler;
4 | import net.sourceforge.pdfjumbler.pdf.PdfEditor;
5 | import net.sourceforge.pdfjumbler.pdf.PdfProcessingFactory;
6 | import org.tinylog.Logger;
7 |
8 | import javax.swing.*;
9 | import java.awt.event.ActionEvent;
10 |
11 | public class ChangeEditorAction extends AbstractAction {
12 | private static final long serialVersionUID = -8468488158161906330L;
13 | private final PdfJumbler parent;
14 | private final Class extends PdfEditor> editorClass;
15 |
16 | public ChangeEditorAction(PdfJumbler parent, Class extends PdfEditor> editorClass) {
17 | super(PdfProcessingFactory.getFriendlyName(editorClass));
18 | this.parent = parent;
19 | this.editorClass = editorClass;
20 | }
21 |
22 | @Override
23 | public void actionPerformed(ActionEvent e) {
24 | try {
25 | PdfProcessingFactory.setEditorClass(editorClass);
26 | }
27 | catch (Exception ex) {
28 | Logger.error(ex);
29 | JOptionPane.showMessageDialog(parent, ex.getLocalizedMessage(), ex.getClass().getSimpleName(), JOptionPane.ERROR_MESSAGE);
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/main/java/net/sourceforge/pdfjumbler/actions/ZoomInAction.java:
--------------------------------------------------------------------------------
1 | package net.sourceforge.pdfjumbler.actions;
2 |
3 | import net.sourceforge.pdfjumbler.Icons;
4 | import net.sourceforge.pdfjumbler.PdfJumbler;
5 | import net.sourceforge.pdfjumbler.PdfList;
6 | import net.sourceforge.pdfjumbler.i18n.I18nKeys;
7 | import net.sourceforge.pdfjumbler.i18n.PdfJumblerResources;
8 |
9 | import javax.swing.*;
10 | import java.awt.event.ActionEvent;
11 |
12 | public class ZoomInAction extends AbstractAction {
13 | private static final long serialVersionUID = 7357355236947950441L;
14 | private final PdfJumbler parent;
15 |
16 | public ZoomInAction(PdfJumbler parent) {
17 | super(PdfJumblerResources.getResources().getString(I18nKeys.ZOOM_IN), Icons.ZOOM_IN);
18 | this.parent = parent;
19 | putValue(Action.SHORT_DESCRIPTION, PdfJumblerResources.getResources().getString(I18nKeys.ZOOM_IN));
20 | putValue(Action.ACCELERATOR_KEY, PdfJumblerResources.getResources().getObject(I18nKeys.ACCELERATOR_ZOOM_IN));
21 | }
22 |
23 | @Override
24 | public void actionPerformed(ActionEvent e) {
25 | PdfList mainList = parent.getMainPdfList();
26 | mainList.setThumbnailSize(mainList.getThumbnailSize() + 10);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/main/java/net/sourceforge/pdfjumbler/actions/ReloadingProcessorListener.java:
--------------------------------------------------------------------------------
1 | package net.sourceforge.pdfjumbler.actions;
2 |
3 | import net.sourceforge.pdfjumbler.PdfJumbler;
4 | import net.sourceforge.pdfjumbler.pdf.PdfEditor;
5 | import net.sourceforge.pdfjumbler.pdf.PdfProcessorListener;
6 | import net.sourceforge.pdfjumbler.pdf.PdfRenderer;
7 |
8 | import java.io.IOException;
9 |
10 | public class ReloadingProcessorListener implements PdfProcessorListener {
11 | private final PdfJumbler parent;
12 |
13 | public ReloadingProcessorListener(PdfJumbler parent) {
14 | this.parent = parent;
15 | }
16 |
17 | @Override
18 | public void pdfRendererChanged(PdfRenderer oldRenderer, PdfRenderer newRenderer) {
19 | try {
20 | oldRenderer.close();
21 | }
22 | catch (IOException e) {
23 | throw new RuntimeException(e);
24 | }
25 | finally {
26 | parent.getMainPdfList().updateUI();
27 | parent.getSecondaryPdfList().updateUI();
28 | }
29 | }
30 |
31 | @Override
32 | public void pdfEditorChanged(final PdfEditor oldEditor, final PdfEditor newEditor) {
33 | try {
34 | oldEditor.close();
35 | }
36 | catch (IOException e) {
37 | throw new RuntimeException(e);
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/main/java/net/sourceforge/pdfjumbler/actions/ZoomOutAction.java:
--------------------------------------------------------------------------------
1 | package net.sourceforge.pdfjumbler.actions;
2 |
3 | import net.sourceforge.pdfjumbler.Icons;
4 | import net.sourceforge.pdfjumbler.PdfJumbler;
5 | import net.sourceforge.pdfjumbler.PdfList;
6 | import net.sourceforge.pdfjumbler.i18n.I18nKeys;
7 | import net.sourceforge.pdfjumbler.i18n.PdfJumblerResources;
8 |
9 | import javax.swing.*;
10 | import java.awt.event.ActionEvent;
11 |
12 | public class ZoomOutAction extends AbstractAction {
13 | private static final long serialVersionUID = -8473933777713981409L;
14 | private final PdfJumbler parent;
15 |
16 | public ZoomOutAction(PdfJumbler parent) {
17 | super(PdfJumblerResources.getResources().getString(I18nKeys.ZOOM_OUT), Icons.ZOOM_OUT);
18 | this.parent = parent;
19 | putValue(Action.SHORT_DESCRIPTION, PdfJumblerResources.getResources().getString(I18nKeys.ZOOM_OUT));
20 | putValue(Action.ACCELERATOR_KEY, PdfJumblerResources.getResources().getObject(I18nKeys.ACCELERATOR_ZOOM_OUT));
21 | }
22 |
23 | @Override
24 | public void actionPerformed(ActionEvent e) {
25 | PdfList mainList = parent.getMainPdfList();
26 | mainList.setThumbnailSize(mainList.getThumbnailSize() - 10);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/main/java/net/sourceforge/pdfjumbler/actions/ChangeRendererAction.java:
--------------------------------------------------------------------------------
1 | package net.sourceforge.pdfjumbler.actions;
2 |
3 | import net.sourceforge.pdfjumbler.PdfJumbler;
4 | import net.sourceforge.pdfjumbler.pdf.PdfProcessingFactory;
5 | import net.sourceforge.pdfjumbler.pdf.PdfRenderer;
6 | import org.tinylog.Logger;
7 |
8 | import javax.swing.*;
9 | import java.awt.event.ActionEvent;
10 |
11 | public class ChangeRendererAction extends AbstractAction {
12 | private static final long serialVersionUID = 8330129264136353423L;
13 | private final PdfJumbler parent;
14 | private final Class extends PdfRenderer> rendererClass;
15 |
16 | public ChangeRendererAction(PdfJumbler parent, Class extends PdfRenderer> rendererClass) {
17 | super(PdfProcessingFactory.getFriendlyName(rendererClass));
18 | this.parent = parent;
19 | this.rendererClass = rendererClass;
20 | }
21 |
22 | @Override
23 | public void actionPerformed(ActionEvent e) {
24 | try {
25 | PdfProcessingFactory.setRendererClass(rendererClass);
26 | }
27 | catch (Exception ex) {
28 | Logger.error(ex);
29 | JOptionPane.showMessageDialog(parent, ex.getLocalizedMessage(), ex.getClass().getSimpleName(), JOptionPane.ERROR_MESSAGE);
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/main/java/net/sourceforge/pdfjumbler/jdragdroplist/JDDLTransferData.java:
--------------------------------------------------------------------------------
1 | package net.sourceforge.pdfjumbler.jdragdroplist;
2 |
3 | import java.awt.datatransfer.DataFlavor;
4 | import java.util.ArrayList;
5 | import java.util.List;
6 |
7 | /**
8 | * @author Martin Gropp
9 | */
10 | public final class JDDLTransferData {
11 | public static final DataFlavor DATA_FLAVOR;
12 |
13 | static {
14 | try {
15 | DATA_FLAVOR = new DataFlavor(
16 | DataFlavor.javaJVMLocalObjectMimeType +
17 | ";class=" + JDDLTransferData.class.getCanonicalName()
18 | );
19 | }
20 | catch (ClassNotFoundException e) {
21 | throw new AssertionError(e);
22 | }
23 | }
24 |
25 | private final JDragDropList sourceList;
26 | private final int[] indices;
27 | private final List values;
28 |
29 | public JDDLTransferData(JDragDropList list) {
30 | this.sourceList = list;
31 | this.indices = list.getSelectedIndices().clone();
32 | this.values = new ArrayList<>(list.getSelectedValuesList());
33 | }
34 |
35 | public JDragDropList getSourceList() {
36 | return sourceList;
37 | }
38 |
39 | public int[] getIndices() {
40 | return indices;
41 | }
42 |
43 | public List getValuesList() {
44 | return values;
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/main/java/net/sourceforge/pdfjumbler/actions/UndoAction.java:
--------------------------------------------------------------------------------
1 | package net.sourceforge.pdfjumbler.actions;
2 |
3 | import net.sourceforge.pdfjumbler.i18n.I18nKeys;
4 | import net.sourceforge.pdfjumbler.i18n.PdfJumblerResources;
5 | import org.tinylog.Logger;
6 |
7 | import javax.swing.*;
8 | import javax.swing.undo.UndoManager;
9 | import java.awt.event.ActionEvent;
10 |
11 | public class UndoAction extends AbstractAction {
12 | private static final long serialVersionUID = 4824090977507378704L;
13 | private final UndoManager undoManager;
14 |
15 | public UndoAction(UndoManager undoManager) {
16 | super(PdfJumblerResources.getResources().getString(I18nKeys.UNDO));
17 | this.undoManager = undoManager;
18 | putValue(Action.SHORT_DESCRIPTION, PdfJumblerResources.getResources().getString(I18nKeys.UNDO));
19 | putValue(Action.ACCELERATOR_KEY, PdfJumblerResources.getResources().getObject(I18nKeys.ACCELERATOR_UNDO));
20 | }
21 |
22 | @Override
23 | public void actionPerformed(ActionEvent e) {
24 | Logger.debug("Can undo: {}", undoManager.canUndo());
25 | if (undoManager.canUndo()) {
26 | Logger.debug("Undoing: {}", undoManager.getPresentationName());
27 | undoManager.undo();
28 | Logger.debug("Next undo event: {}", undoManager.getPresentationName());
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/main/java/net/sourceforge/pdfjumbler/actions/ViewThumbnailsAction.java:
--------------------------------------------------------------------------------
1 | package net.sourceforge.pdfjumbler.actions;
2 |
3 | import net.sourceforge.pdfjumbler.ConfigKeys;
4 | import net.sourceforge.pdfjumbler.PdfJumbler;
5 | import net.sourceforge.pdfjumbler.PdfList;
6 | import net.sourceforge.pdfjumbler.i18n.I18nKeys;
7 | import net.sourceforge.pdfjumbler.i18n.PdfJumblerResources;
8 |
9 | import javax.swing.AbstractAction;
10 | import javax.swing.Action;
11 | import java.awt.event.ActionEvent;
12 | import java.util.prefs.Preferences;
13 |
14 | public class ViewThumbnailsAction extends AbstractAction {
15 | private static final long serialVersionUID = 7357355236947950441L;
16 | private final PdfJumbler parent;
17 |
18 | public ViewThumbnailsAction(PdfJumbler parent) {
19 | super(PdfJumblerResources.getResources().getString(I18nKeys.VIEW_THUMBNAILS));
20 | this.parent = parent;
21 | putValue(Action.SHORT_DESCRIPTION, PdfJumblerResources.getResources().getString(I18nKeys.VIEW_THUMBNAILS));
22 | }
23 |
24 | @Override
25 | public void actionPerformed(ActionEvent e) {
26 | parent.getMainPdfList().setShowCellText(false);
27 | parent.getSecondaryPdfList().setShowCellText(false);
28 | Preferences.userNodeForPackage(PdfJumbler.class).putBoolean(ConfigKeys.SHOW_TEXT, false);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/main/java/net/sourceforge/pdfjumbler/actions/ViewListAction.java:
--------------------------------------------------------------------------------
1 | package net.sourceforge.pdfjumbler.actions;
2 |
3 | import net.sourceforge.pdfjumbler.ConfigKeys;
4 | import net.sourceforge.pdfjumbler.Icons;
5 | import net.sourceforge.pdfjumbler.PdfJumbler;
6 | import net.sourceforge.pdfjumbler.PdfList;
7 | import net.sourceforge.pdfjumbler.i18n.I18nKeys;
8 | import net.sourceforge.pdfjumbler.i18n.PdfJumblerResources;
9 |
10 | import javax.swing.AbstractAction;
11 | import javax.swing.Action;
12 | import java.awt.event.ActionEvent;
13 | import java.util.prefs.Preferences;
14 |
15 | public class ViewListAction extends AbstractAction {
16 | private static final long serialVersionUID = 7357355236947950441L;
17 | private final PdfJumbler parent;
18 |
19 | public ViewListAction(PdfJumbler parent) {
20 | super(PdfJumblerResources.getResources().getString(I18nKeys.VIEW_LIST));
21 | this.parent = parent;
22 | putValue(Action.SHORT_DESCRIPTION, PdfJumblerResources.getResources().getString(I18nKeys.VIEW_LIST));
23 | }
24 |
25 | @Override
26 | public void actionPerformed(ActionEvent e) {
27 | parent.getMainPdfList().setShowCellText(true);
28 | parent.getSecondaryPdfList().setShowCellText(true);
29 | Preferences.userNodeForPackage(PdfJumbler.class).putBoolean(ConfigKeys.SHOW_TEXT, true);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/main/java/net/sourceforge/pdfjumbler/actions/AboutAction.java:
--------------------------------------------------------------------------------
1 | package net.sourceforge.pdfjumbler.actions;
2 |
3 | import net.sourceforge.pdfjumbler.PdfJumbler;
4 | import net.sourceforge.pdfjumbler.i18n.I18nKeys;
5 | import net.sourceforge.pdfjumbler.i18n.PdfJumblerResources;
6 | import net.sourceforge.pdfjumbler.pdf.PdfProcessingFactory;
7 |
8 | import javax.swing.*;
9 | import java.awt.event.ActionEvent;
10 |
11 | public class AboutAction extends AbstractAction {
12 | private static final long serialVersionUID = -6505580153294146608L;
13 | private final PdfJumbler parent;
14 |
15 | public AboutAction(PdfJumbler parent) {
16 | super(PdfJumblerResources.getResources().getString(I18nKeys.ABOUT));
17 | this.parent = parent;
18 | putValue(Action.SHORT_DESCRIPTION, PdfJumblerResources.getResources().getString(I18nKeys.ABOUT));
19 | }
20 |
21 | @Override
22 | public void actionPerformed(ActionEvent e) {
23 | JOptionPane.showMessageDialog(
24 | parent,
25 | String.format(
26 | PdfJumblerResources.getResources().getString(I18nKeys.ABOUT_TEXT),
27 | PdfJumbler.VERSION_STRING,
28 | PdfProcessingFactory.getEditor().getClass().getCanonicalName(),
29 | PdfProcessingFactory.getRenderer().getClass().getCanonicalName()
30 | ),
31 | PdfJumblerResources.getResources().getString(I18nKeys.ABOUT_TITLE),
32 | JOptionPane.INFORMATION_MESSAGE
33 | );
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/main/java/net/sourceforge/pdfjumbler/actions/SaveDocumentWorker.java:
--------------------------------------------------------------------------------
1 | package net.sourceforge.pdfjumbler.actions;
2 |
3 | import net.sourceforge.pdfjumbler.DocumentManager;
4 | import net.sourceforge.pdfjumbler.jdragdroplist.UndoableListModel;
5 | import net.sourceforge.pdfjumbler.pdf.Page;
6 | import net.sourceforge.pdfjumbler.pdf.PdfProcessingFactory;
7 |
8 | import javax.swing.*;
9 | import java.io.File;
10 | import java.nio.file.Files;
11 | import java.util.List;
12 |
13 | public class SaveDocumentWorker extends SwingWorker {
14 | private final List pages;
15 | private final File file;
16 |
17 | public SaveDocumentWorker(UndoableListModel model, File file) {
18 | this.pages = model.getList();
19 | this.file = file;
20 | }
21 |
22 | @Override
23 | protected Void doInBackground() throws Exception {
24 | firePropertyChange("note", "", file.getName());
25 |
26 | // TODO: Detect links! (java.nio.file?)
27 |
28 | if (file.exists() && DocumentManager.getAllFiles().contains(file)) {
29 | File tempFile = File.createTempFile("pdfjumbler", ".pdf");
30 | Files.move(file.toPath(), tempFile.toPath());
31 | tempFile.deleteOnExit();
32 |
33 | for (Page page : DocumentManager.getPages(file)) {
34 | page.setFile(tempFile);
35 | }
36 | }
37 |
38 | PdfProcessingFactory.getEditor().saveDocument(
39 | pages,
40 | file
41 | );
42 |
43 | return null;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/main/java/net/sourceforge/pdfjumbler/actions/ClearAction.java:
--------------------------------------------------------------------------------
1 | package net.sourceforge.pdfjumbler.actions;
2 |
3 | import net.sourceforge.pdfjumbler.PdfJumbler;
4 | import net.sourceforge.pdfjumbler.i18n.I18nKeys;
5 | import net.sourceforge.pdfjumbler.i18n.PdfJumblerResources;
6 | import net.sourceforge.pdfjumbler.jdragdroplist.UndoableListModel;
7 | import net.sourceforge.pdfjumbler.pdf.Page;
8 |
9 | import javax.swing.*;
10 | import java.awt.event.ActionEvent;
11 |
12 | public class ClearAction extends AbstractAction {
13 | private static final long serialVersionUID = 8258949195959150573L;
14 | private final PdfJumbler parent;
15 |
16 | public ClearAction(PdfJumbler parent) {
17 | super(PdfJumblerResources.getResources().getString(I18nKeys.CLEAR_LIST), null);
18 | this.parent = parent;
19 | putValue(Action.SHORT_DESCRIPTION, PdfJumblerResources.getResources().getString(I18nKeys.CLEAR_LIST));
20 | }
21 |
22 | @Override
23 | public void actionPerformed(ActionEvent e) {
24 | UndoableListModel model = parent.getMainPdfList().getModel();
25 | if (
26 | (model.getSize() > 0) &&
27 | (
28 | JOptionPane.showConfirmDialog(
29 | parent,
30 | PdfJumblerResources.getResources().getString(I18nKeys.CONFIRM_CLEAR_LIST_TEXT),
31 | PdfJumblerResources.getResources().getString(I18nKeys.CONFIRM_CLEAR_LIST_TITLE),
32 | JOptionPane.OK_CANCEL_OPTION,
33 | JOptionPane.WARNING_MESSAGE
34 | ) == JOptionPane.OK_OPTION
35 | )
36 | ) {
37 | model.clear();
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/main/java/net/sourceforge/pdfjumbler/actions/MoveUpAction.java:
--------------------------------------------------------------------------------
1 | package net.sourceforge.pdfjumbler.actions;
2 |
3 | import net.sourceforge.pdfjumbler.i18n.I18nKeys;
4 | import net.sourceforge.pdfjumbler.i18n.PdfJumblerResources;
5 | import net.sourceforge.pdfjumbler.jdragdroplist.JDragDropList;
6 | import net.sourceforge.pdfjumbler.jdragdroplist.UndoableListModel;
7 | import net.sourceforge.pdfjumbler.pdf.Page;
8 |
9 | import javax.swing.*;
10 | import java.awt.event.ActionEvent;
11 |
12 | public class MoveUpAction extends AbstractAction {
13 | private static final long serialVersionUID = 4204383549863556707L;
14 |
15 | public MoveUpAction() {
16 | super(PdfJumblerResources.getResources().getString(I18nKeys.MOVE_UP));
17 | putValue(Action.SHORT_DESCRIPTION, PdfJumblerResources.getResources().getString(I18nKeys.MOVE_UP));
18 | putValue(Action.ACCELERATOR_KEY, PdfJumblerResources.getResources().getObject(I18nKeys.ACCELERATOR_MOVE_UP));
19 | }
20 |
21 | @Override
22 | public void actionPerformed(ActionEvent e) {
23 | if (!(e.getSource() instanceof JDragDropList>)) {
24 | return;
25 | }
26 |
27 | @SuppressWarnings({"unchecked", "rawtypes"})
28 | JDragDropList list = (JDragDropList)e.getSource();
29 | int index = list.getSelectedIndex();
30 | if (index <= 0) {
31 | return;
32 | }
33 |
34 | UndoableListModel model = list.getModel();
35 | Page page = model.get(index);
36 | model.remove(index);
37 | model.add(index-1, page);
38 |
39 | list.setSelectedIndex(index-1);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/main/java/net/sourceforge/pdfjumbler/DocumentManager.java:
--------------------------------------------------------------------------------
1 | package net.sourceforge.pdfjumbler;
2 |
3 | import java.io.File;
4 | import java.io.IOException;
5 | import java.util.ArrayList;
6 | import java.util.Collection;
7 | import java.util.Collections;
8 | import java.util.HashMap;
9 | import java.util.List;
10 | import java.util.Map;
11 |
12 | import net.sourceforge.pdfjumbler.pdf.Page;
13 | import net.sourceforge.pdfjumbler.pdf.PdfProcessingFactory;
14 |
15 | /**
16 | * Creates & keeps track of open pages.
17 | *
18 | * @author Martin Gropp
19 | */
20 | public class DocumentManager {
21 | private static final Map> pageMap = new HashMap<>();
22 |
23 | public static List getPages(File file) throws IOException {
24 | if (pageMap.containsKey(file)) {
25 | return pageMap.get(file);
26 | }
27 |
28 | int pageCount = PdfProcessingFactory.getRenderer().getNumberOfPages(file);
29 | List pages = new ArrayList<>(pageCount);
30 | for (int i = 0; i < pageCount; i++) {
31 | Page page = new Page(file, i);
32 | pages.add(page);
33 | }
34 |
35 | pages = Collections.unmodifiableList(pages);
36 | pageMap.put(file, pages);
37 |
38 | return pages;
39 | }
40 |
41 | public static Collection getAllFiles() {
42 | return Collections.unmodifiableSet(pageMap.keySet());
43 | }
44 |
45 | public static Collection getAllPages() {
46 | ArrayList pages = new ArrayList<>();
47 | for (List pageList : pageMap.values()) {
48 | pages.addAll(pageList);
49 | }
50 |
51 | return pages;
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/main/java/net/sourceforge/pdfjumbler/actions/MoveDownAction.java:
--------------------------------------------------------------------------------
1 | package net.sourceforge.pdfjumbler.actions;
2 |
3 | import net.sourceforge.pdfjumbler.i18n.I18nKeys;
4 | import net.sourceforge.pdfjumbler.i18n.PdfJumblerResources;
5 | import net.sourceforge.pdfjumbler.jdragdroplist.JDragDropList;
6 | import net.sourceforge.pdfjumbler.jdragdroplist.UndoableListModel;
7 | import net.sourceforge.pdfjumbler.pdf.Page;
8 |
9 | import javax.swing.*;
10 | import java.awt.event.ActionEvent;
11 |
12 | public class MoveDownAction extends AbstractAction {
13 | private static final long serialVersionUID = -4468213711264218766L;
14 |
15 | public MoveDownAction() {
16 | super(PdfJumblerResources.getResources().getString(I18nKeys.MOVE_DOWN));
17 | putValue(Action.SHORT_DESCRIPTION, PdfJumblerResources.getResources().getString(I18nKeys.MOVE_DOWN));
18 | putValue(Action.ACCELERATOR_KEY, PdfJumblerResources.getResources().getObject(I18nKeys.ACCELERATOR_MOVE_DOWN));
19 | }
20 |
21 | @Override
22 | public void actionPerformed(ActionEvent e) {
23 | if (!(e.getSource() instanceof JDragDropList>)) {
24 | return;
25 | }
26 |
27 | @SuppressWarnings({"unchecked", "rawtypes"})
28 | JDragDropList list = (JDragDropList)e.getSource();
29 | UndoableListModel model = list.getModel();
30 |
31 | int index = list.getSelectedIndex();
32 | if ((index < 0) || (index >= model.getSize()-1)) {
33 | return;
34 | }
35 |
36 | Page page = model.get(index);
37 | model.remove(index);
38 | model.add(index+1, page);
39 |
40 | list.setSelectedIndex(index+1);
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/main/java/net/sourceforge/pdfjumbler/pdf/PdfRenderer.java:
--------------------------------------------------------------------------------
1 | package net.sourceforge.pdfjumbler.pdf;
2 |
3 | import java.awt.Image;
4 | import java.io.File;
5 | import java.io.IOException;
6 |
7 | /**
8 | * @author Martin Gropp
9 | */
10 | public interface PdfRenderer {
11 | /**
12 | * Render a page from a PDF document as an image.
13 | * Currently, it is assumed that PDF editor and renderer are independent,
14 | * so this method should not rely on the document being open in a particular
15 | * editor. Also, even if the document is opened in a certain editor when the
16 | * renderer is initialized, the editor might change.
17 | * maxWidth and maxHeight specify the maximum size in the respective dimension;
18 | * due to the fixed aspect ratio, the actual size might be lower.
19 | *
20 | * @param page
21 | * The page to be renderered.
22 | * @param maxWidth
23 | * The maximum image width.
24 | * @param maxHeight
25 | * The maximum image height.
26 | * @return
27 | * An Image of the PDF page.
28 | * @throws IOException
29 | */
30 | Image renderPage(Page page, int maxWidth, int maxHeight) throws IOException;
31 |
32 | /**
33 | * Return the number of pages in a PDF file.
34 | *
35 | * @param file
36 | * @return
37 | * The number of pages in the file.
38 | * @throws IOException
39 | */
40 | int getNumberOfPages(File file) throws IOException;
41 |
42 | /**
43 | * Close the editor, for example because a different editor is selected.
44 | *
45 | * @throws IOException
46 | */
47 | void close() throws IOException;
48 | }
49 |
--------------------------------------------------------------------------------
/src/main/resources/icons/rotate-ccw.svg:
--------------------------------------------------------------------------------
1 |
2 |
20 |
--------------------------------------------------------------------------------
/src/main/resources/icons/rotate-cw.svg:
--------------------------------------------------------------------------------
1 |
2 |
20 |
--------------------------------------------------------------------------------
/src/main/java/net/sourceforge/pdfjumbler/actions/DelAction.java:
--------------------------------------------------------------------------------
1 | package net.sourceforge.pdfjumbler.actions;
2 |
3 | import net.sourceforge.pdfjumbler.Icons;
4 | import net.sourceforge.pdfjumbler.PdfJumbler;
5 | import net.sourceforge.pdfjumbler.i18n.I18nKeys;
6 | import net.sourceforge.pdfjumbler.i18n.PdfJumblerResources;
7 | import net.sourceforge.pdfjumbler.jdragdroplist.JDragDropList;
8 | import net.sourceforge.pdfjumbler.jdragdroplist.UndoableListModel;
9 | import net.sourceforge.pdfjumbler.pdf.Page;
10 |
11 | import javax.swing.*;
12 | import java.awt.event.ActionEvent;
13 | import java.util.Arrays;
14 |
15 | public class DelAction extends AbstractAction {
16 | private static final long serialVersionUID = 8258949195959150573L;
17 | private final PdfJumbler parent;
18 |
19 | public DelAction(PdfJumbler parent) {
20 | super(PdfJumblerResources.getResources().getString(I18nKeys.DELETE), Icons.EDIT_DELETE);
21 | this.parent = parent;
22 | putValue(Action.SHORT_DESCRIPTION, PdfJumblerResources.getResources().getString(I18nKeys.DELETE));
23 | putValue(Action.ACCELERATOR_KEY, PdfJumblerResources.getResources().getObject(I18nKeys.ACCELERATOR_DELETE));
24 | }
25 |
26 | @SuppressWarnings("unchecked")
27 | @Override
28 | public void actionPerformed(ActionEvent e) {
29 | JDragDropList list;
30 | if (e.getSource() instanceof JDragDropList) {
31 | list = (JDragDropList)e.getSource();
32 | } else {
33 | list = parent.getMainPdfList();
34 | }
35 | UndoableListModel model = list.getModel();
36 | int[] selected = list.getSelectedIndices();
37 | Arrays.sort(selected);
38 | for (int i = selected.length-1; i >= 0; i--) {
39 | model.remove(selected[i]);
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/main/java/net/sourceforge/pdfjumbler/pdfbox/PdfEditor.java:
--------------------------------------------------------------------------------
1 | package net.sourceforge.pdfjumbler.pdfbox;
2 |
3 | import java.io.File;
4 | import java.io.IOException;
5 | import java.util.HashMap;
6 | import java.util.List;
7 | import java.util.Map;
8 |
9 | import net.sourceforge.pdfjumbler.pdf.Page;
10 |
11 | import org.apache.pdfbox.pdmodel.PDDocument;
12 |
13 | import org.apache.pdfbox.pdmodel.PDPage;
14 | import org.tinylog.Logger;
15 |
16 | /**
17 | * PdfJumbler interface to PDFBox.
18 | *
19 | * @author Martin Gropp
20 | */
21 | public class PdfEditor implements net.sourceforge.pdfjumbler.pdf.PdfEditor {
22 | public static String getFriendlyName() {
23 | return "PDFBox";
24 | }
25 |
26 | @Override
27 | public void close() {}
28 |
29 | @Override
30 | public void saveDocument(List pages, File file) throws IOException {
31 | Logger.info("Saving {} pages to {}", pages.size(), file);
32 |
33 | if (pages.size() == 0) {
34 | throw new IOException("Empty document.");
35 | }
36 |
37 | Map docs = new HashMap<>();
38 | try (PDDocument outDoc = new PDDocument()) {
39 | for (Page page : pages) {
40 | PDDocument pageDoc = docs.get(page.getFile());
41 | if (pageDoc == null) {
42 | pageDoc = PDDocument.load(page.getFile());
43 | docs.put(page.getFile(), pageDoc);
44 | }
45 |
46 | PDPage pdPage = pageDoc.getPage(page.getIndex());
47 | pdPage.setRotation(page.getRotation());
48 |
49 | outDoc.addPage(pdPage);
50 | }
51 |
52 | outDoc.save(file.toString());
53 | Logger.info("File saved successfully.");
54 | }
55 | finally {
56 | for (PDDocument doc : docs.values()) {
57 | doc.close();
58 | }
59 | }
60 | }
61 |
62 | }
--------------------------------------------------------------------------------
/setup/ubuntu/install.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # By Alex Zaslavskis and Martin Gropp
3 | set -e
4 | set -u
5 |
6 | dest_dir="/opt/pdfjumbler"
7 | desktop_file="/usr/share/applications/pdfjumbler.desktop"
8 | jar_file="$dest_dir/pdfjumbler.jar"
9 | jar_file_url=$(
10 | curl https://api.github.com/repos/mgropp/pdfjumbler/releases/latest \
11 | | grep browser_download_url \
12 | | grep .jar \
13 | | cut -d '"' -f 4 \
14 | )
15 | launcher_file="/usr/local/bin/pdfjumbler"
16 |
17 | # Make sure Java is installed
18 | if command -v java >/dev/null 2>&1
19 | then
20 | echo "Java found."
21 | echo "Version:"
22 | java -version
23 | else
24 | echo "Java not found. Please install Java!"
25 | exit 1
26 | fi
27 |
28 | # Create temp directory
29 | tmpdir=$( mktemp -d )
30 | trap "rm -rf \"$tmpdir\"" EXIT
31 | cd "$tmpdir"
32 |
33 | # Create launcher
34 | cat > pdfjumbler << EOF
35 | #!/bin/sh
36 | exec java -jar "$jar_file" "\$@"
37 | EOF
38 |
39 | # Create desktop file
40 | cat > pdfjumbler.desktop << EOF
41 | [Desktop Entry]
42 | Name=PdfJumbler
43 | Exec=$launcher_file
44 | Icon=pdf
45 | Type=Application
46 | Categories=GTK;GNOME;Utility;
47 | Terminal=false
48 | EOF
49 |
50 | # Create script to move files and set permissions
51 | cat > install.sh << EOF
52 | #!/bin/sh
53 | mv pdfjumbler.desktop "$desktop_file"
54 | chown root:root "$desktop_file"
55 | chmod 644 "$desktop_file"
56 |
57 | mkdir -p "$dest_dir"
58 | mv pdfjumbler.jar "$jar_file"
59 | chown -R root:root "$dest_dir"
60 | chmod 755 "$dest_dir"
61 | chmod 644 "$jar_file"
62 |
63 | mv pdfjumbler "$launcher_file"
64 | chown root:root "$launcher_file"
65 | chmod 755 "$launcher_file"
66 | EOF
67 |
68 | # Download
69 | echo "Downloading files"
70 | curl -L "$jar_file_url" -o pdfjumbler.jar
71 |
72 | # Install
73 | sudo sh install.sh
74 |
75 | echo
76 | echo "Done. Enjoy!"
77 |
--------------------------------------------------------------------------------
/src/main/java/net/sourceforge/pdfjumbler/actions/OpenDocumentWorker.java:
--------------------------------------------------------------------------------
1 | package net.sourceforge.pdfjumbler.actions;
2 |
3 | import net.sourceforge.pdfjumbler.DocumentManager;
4 | import net.sourceforge.pdfjumbler.jdragdroplist.UndoableListModel;
5 | import net.sourceforge.pdfjumbler.pdf.Page;
6 | import org.tinylog.Logger;
7 |
8 | import javax.swing.*;
9 | import java.io.File;
10 | import java.util.Collection;
11 | import java.util.List;
12 | import java.util.concurrent.atomic.AtomicInteger;
13 |
14 | public class OpenDocumentWorker extends SwingWorker> {
15 | private final UndoableListModel model;
16 | private final AtomicInteger insertPos;
17 | private final Collection files;
18 |
19 | public OpenDocumentWorker(UndoableListModel model, int insertPos, Collection files) {
20 | this.model = model;
21 | this.insertPos = new AtomicInteger((insertPos < 0) ? model.getSize() : insertPos);
22 | this.files = files;
23 |
24 | // execute is final :/
25 | model.beginCompoundEdit("Open");
26 | }
27 |
28 | @Override
29 | protected Void doInBackground() throws Exception {
30 | setProgress(0);
31 |
32 | int nFiles = 0;
33 | for (File file : files) {
34 | Logger.info("Opening file: {}", file);
35 | firePropertyChange("note", "", file.getName());
36 |
37 | publish(DocumentManager.getPages(file));
38 |
39 | nFiles++;
40 | setProgress((int)(100 * (nFiles / (double)files.size())));
41 | }
42 |
43 | return null;
44 | }
45 |
46 | @Override
47 | protected void process(List> pages) {
48 | for (List pageList : pages) {
49 | for (Page page : pageList) {
50 | model.add(
51 | insertPos.getAndIncrement(),
52 | page
53 | );
54 | }
55 | }
56 | }
57 |
58 | @Override
59 | protected void done() {
60 | model.endCompoundEdit();
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/main/java/net/sourceforge/pdfjumbler/TrashDropTargetListener.java:
--------------------------------------------------------------------------------
1 | package net.sourceforge.pdfjumbler;
2 |
3 | import java.awt.datatransfer.UnsupportedFlavorException;
4 | import java.awt.dnd.DnDConstants;
5 | import java.awt.dnd.DropTargetDragEvent;
6 | import java.awt.dnd.DropTargetDropEvent;
7 | import java.awt.dnd.DropTargetEvent;
8 | import java.awt.dnd.DropTargetListener;
9 | import java.io.IOException;
10 | import java.util.Arrays;
11 |
12 | import net.sourceforge.pdfjumbler.jdragdroplist.JDDLTransferData;
13 | import net.sourceforge.pdfjumbler.pdf.Page;
14 |
15 | /**
16 | * @author Martin Gropp
17 | */
18 | public class TrashDropTargetListener implements DropTargetListener {
19 | @Override
20 | public void dragEnter(DropTargetDragEvent dtde) {
21 | if (
22 | ((dtde.getDropAction() & DnDConstants.ACTION_MOVE) != 0) &&
23 | (dtde.getTransferable().isDataFlavorSupported(JDDLTransferData.DATA_FLAVOR))
24 | ) {
25 | dtde.acceptDrag(DnDConstants.ACTION_MOVE);
26 | } else {
27 | dtde.rejectDrag();
28 | }
29 | }
30 |
31 | @Override
32 | public void dragOver(DropTargetDragEvent dtde) {
33 | dragEnter(dtde);
34 | }
35 |
36 | @SuppressWarnings("unchecked")
37 | @Override
38 | public void drop(DropTargetDropEvent dtde) {
39 | if (dtde.getTransferable().isDataFlavorSupported(JDDLTransferData.DATA_FLAVOR)) {
40 | JDDLTransferData data;
41 | try {
42 | data = (JDDLTransferData)dtde.getTransferable().getTransferData(JDDLTransferData.DATA_FLAVOR);
43 | }
44 | catch (UnsupportedFlavorException | IOException e) {
45 | throw new AssertionError(e);
46 | }
47 |
48 | int[] indices = data.getIndices();
49 | Arrays.sort(indices);
50 | for (int i = indices.length-1; i >= 0; i--) {
51 | PdfList.removeItem(data.getSourceList(), indices[i]);
52 | }
53 | }
54 | }
55 |
56 | @Override
57 | public void dragExit(DropTargetEvent dte) {
58 | }
59 |
60 | @Override
61 | public void dropActionChanged(DropTargetDragEvent dtde) {
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/main/resources/icons/edit-delete.svg:
--------------------------------------------------------------------------------
1 |
2 |
61 |
--------------------------------------------------------------------------------
/src/main/java/net/sourceforge/pdfjumbler/jdragdroplist/ListEdit.java:
--------------------------------------------------------------------------------
1 | package net.sourceforge.pdfjumbler.jdragdroplist;
2 |
3 | import java.util.ArrayList;
4 | import java.util.Collection;
5 |
6 | import javax.swing.undo.AbstractUndoableEdit;
7 |
8 | class ListEdit extends AbstractUndoableEdit {
9 | private static final long serialVersionUID = 1158687785913642971L;
10 |
11 | public enum Type {
12 | ADD, ADD_MULTIPLE,
13 | REMOVE, REMOVE_MULTIPLE,
14 | SET
15 | }
16 |
17 | final Type type;
18 | final UndoableList list;
19 | final UndoableList list2;
20 | final int index;
21 |
22 | final T item;
23 | final T item2;
24 | final Collection items;
25 |
26 | ListEdit(Type type, UndoableList list, int index, T item) {
27 | this(type, list, index, item, null);
28 | }
29 |
30 | ListEdit(Type type, UndoableList list, int index, T item, T item2) {
31 | if ((type != Type.ADD) && (type != Type.REMOVE) && (type != Type.SET)) {
32 | throw new IllegalArgumentException();
33 | }
34 |
35 | this.type = type;
36 | this.list = list;
37 | this.list2 = null;
38 | this.index = index;
39 | this.item = item;
40 | this.item2 = item2;
41 | this.items = null;
42 | }
43 |
44 | ListEdit(Type type, UndoableList list, int index, Collection extends T> items) {
45 | if ((type != Type.ADD_MULTIPLE) && (type != Type.REMOVE_MULTIPLE)) {
46 | throw new IllegalArgumentException();
47 | }
48 |
49 | this.type = type;
50 | this.list = list;
51 | this.list2 = null;
52 | this.index = index;
53 | this.item = null;
54 | this.item2 = null;
55 | this.items = new ArrayList<>(items);
56 | }
57 |
58 | @Override
59 | public void undo() {
60 | list.undo(this);
61 | }
62 |
63 | @Override
64 | public void redo() {
65 | list.redo(this);
66 | }
67 |
68 | @Override
69 | public String toString() {
70 | if (items == null) {
71 | return
72 | type + " @ " + index + ": " + item +
73 | ((item2 == null) ? "" : " -> " + item2);
74 | } else {
75 | return
76 | type + " @ " + index + ": " + items;
77 | }
78 | }
79 |
80 | @Override
81 | public String getPresentationName() {
82 | return toString();
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/src/main/java/net/sourceforge/pdfjumbler/actions/DocOpenAction.java:
--------------------------------------------------------------------------------
1 | package net.sourceforge.pdfjumbler.actions;
2 |
3 | import net.sourceforge.pdfjumbler.Icons;
4 | import net.sourceforge.pdfjumbler.PdfJumbler;
5 | import net.sourceforge.pdfjumbler.PdfList;
6 | import net.sourceforge.pdfjumbler.i18n.I18nKeys;
7 | import net.sourceforge.pdfjumbler.i18n.PdfJumblerResources;
8 |
9 | import javax.swing.*;
10 | import javax.swing.filechooser.FileFilter;
11 | import java.awt.event.ActionEvent;
12 | import java.io.File;
13 | import java.util.Arrays;
14 |
15 | public class DocOpenAction extends AbstractAction {
16 | private static final long serialVersionUID = -1652454884847492822L;
17 |
18 | private static String lastOpenedFileLocation = null;
19 | private final PdfJumbler parent;
20 | private final PdfList list;
21 |
22 | public DocOpenAction(PdfJumbler parent, PdfList list) {
23 | this(parent, list, Icons.DOCUMENT_OPEN);
24 | }
25 |
26 | public DocOpenAction(PdfJumbler parent, PdfList list, Icon icon) {
27 | super(PdfJumblerResources.getResources().getString(I18nKeys.OPEN_DOCUMENT), icon);
28 | this.parent = parent;
29 | this.list = list;
30 |
31 | putValue(Action.SHORT_DESCRIPTION, PdfJumblerResources.getResources().getString(I18nKeys.OPEN_DOCUMENT));
32 | putValue(Action.ACCELERATOR_KEY, PdfJumblerResources.getResources().getObject(I18nKeys.ACCELERATOR_OPEN));
33 | }
34 |
35 | @Override
36 | public void actionPerformed(ActionEvent e) {
37 | JFileChooser chooser = new JFileChooser(DocOpenAction.lastOpenedFileLocation);
38 | chooser.setMultiSelectionEnabled(true);
39 | chooser.setFileFilter(new FileFilter() {
40 | @Override
41 | public boolean accept(File file) {
42 | DocOpenAction.lastOpenedFileLocation = file.getParent();
43 | return
44 | file.isDirectory() ||
45 | (file.exists() && file.isFile() && file.canRead() && file.getName().toLowerCase().endsWith(".pdf"));
46 | }
47 |
48 | @Override
49 | public String getDescription() {
50 | return PdfJumblerResources.getResources().getString(I18nKeys.FILE_FILTER_PDF);
51 | }
52 |
53 | });
54 |
55 | if (chooser.showOpenDialog(parent) == JFileChooser.APPROVE_OPTION) {
56 | PdfJumbler.openFiles(list, -1, Arrays.asList(chooser.getSelectedFiles()));
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/main/java/net/sourceforge/pdfjumbler/i18n/I18nKeys.java:
--------------------------------------------------------------------------------
1 | package net.sourceforge.pdfjumbler.i18n;
2 |
3 | public interface I18nKeys {
4 | String OPEN_DOCUMENT = "OPEN_DOCUMENT";
5 | String SAVE_DOCUMENT = "SAVE_DOCUMENT";
6 | String FILE_FILTER_PDF = "FILE_FILTER_PDF";
7 | String OVERWRITE_FILE_TITLE = "OVERWRITE_FILE_TITLE";
8 | String OVERWRITE_FILE_TEXT = "OVERWRITE_FILE_TEXT";
9 | String ZOOM_OUT = "ZOOM_OUT";
10 | String ZOOM_IN = "ZOOM_IN";
11 | String CLEAR_LIST = "CLEAR_LIST";
12 | String DELETE = "DELETE";
13 | String ABOUT = "ABOUT";
14 | String LIST_DROP_PDFS_TO_EDIT = "LIST_DROP_PDFS_TO_EDIT";
15 | String LIST_CLIPBOARD_EMPTY = "LIST_CLIPBOARD_EMPTY";
16 | String CONFIRM_CLEAR_LIST_TITLE = "CONFIRM_CLEAR_LIST_TITLE";
17 | String CONFIRM_CLEAR_LIST_TEXT = "CONFIRM_CLEAR_LIST_TEXT";
18 | String NO_PDF_EDITOR_TITLE = "NO_PDF_EDITOR_TITLE";
19 | String NO_PDF_EDITOR_TEXT = "NO_PDF_EDITOR_TEXT";
20 | String NO_PDF_RENDERER_TITLE = "NO_PDF_RENDERER_TITLE";
21 | String NO_PDF_RENDERER_TEXT = "NO_PDF_RENDERER_TEXT";
22 | String PDF_PAGE_TITLE = "PDF_PAGE_TITLE";
23 | String MENU = "MENU";
24 | String MENU_VIEW = "MENU_VIEW";
25 | String MENU_EDITOR = "MENU_EDIT";
26 | String MENU_RENDERER = "MENU_RENDER";
27 | String MOVE_UP = "MOVE_UP";
28 | String MOVE_DOWN = "MOVE_DOWN";
29 | String PROGRESS_ABORT = "PROGRESS_ABORT";
30 | String UNDO = "UNDO";
31 | String REDO = "REDO";
32 | String ROTATE_CW = "ROTATE_CW";
33 | String ROTATE_CCW = "ROTATE_CCW";
34 | String ACCELERATOR_OPEN = "ACCELERATOR_OPEN";
35 | String ACCELERATOR_SAVE = "ACCELERATOR_SAVE";
36 | String ACCELERATOR_ZOOM_IN = "ACCELERATOR_ZOOM_IN";
37 | String ACCELERATOR_ZOOM_OUT = "ACCELERATOR_ZOOM_OUT";
38 | String ACCELERATOR_MOVE_UP = "ACCELERATOR_MOVE_UP";
39 | String ACCELERATOR_MOVE_DOWN = "ACCELERATOR_MOVE_DOWN";
40 | String ACCELERATOR_DELETE = "ACCELERATOR_DELETE";
41 | String ACCELERATOR_UNDO = "ACCELERATOR_UNDO";
42 | String ACCELERATOR_REDO = "ACCELERATOR_REDO";
43 | String ACCELERATOR_ROTATE_CW = "ACCELERATOR_ROTATE_CW";
44 | String ACCELERATOR_ROTATE_CCW = "ACCELERATOR_ROTATE_CCW";
45 | String ABOUT_TITLE = "ABOUT_TITLE";
46 | String PLUGIN_ERROR_TITLE = "PLUGIN_ERROR_TITLE";
47 | String PLUGIN_INIT_ERROR = "PLUGIN_INIT_ERROR";
48 | String PLUGIN_ERROR_VERSION_INCOMPATIBLE = "PLUGIN_ERROR_VERSION_INCOMPATIBLE";
49 | String ABOUT_TEXT = "ABOUT_TEXT";
50 | String VIEW_LIST = "VIEW_LIST";
51 | String VIEW_THUMBNAILS = "VIEW_THUMBNAILS";
52 | }
--------------------------------------------------------------------------------
/src/main/java/net/sourceforge/pdfjumbler/jdragdroplist/UndoableListModel.java:
--------------------------------------------------------------------------------
1 | package net.sourceforge.pdfjumbler.jdragdroplist;
2 |
3 | import javax.swing.*;
4 | import javax.swing.event.ListDataEvent;
5 | import javax.swing.event.ListDataListener;
6 | import javax.swing.event.UndoableEditListener;
7 | import javax.swing.undo.CompoundEdit;
8 | import java.util.Collections;
9 | import java.util.LinkedList;
10 | import java.util.List;
11 |
12 | public final class UndoableListModel extends AbstractListModel {
13 | private final UndoableList list;
14 |
15 | public UndoableListModel(UndoableList list) {
16 | this.list = list;
17 | ListDataListener listDataListener = new ListDataListener() {
18 | @Override
19 | public void intervalAdded(ListDataEvent e) {
20 | fireIntervalAdded(this, e.getIndex0(), e.getIndex1());
21 | }
22 |
23 | @Override
24 | public void intervalRemoved(ListDataEvent e) {
25 | fireIntervalRemoved(this, e.getIndex0(), e.getIndex1());
26 | }
27 |
28 | @Override
29 | public void contentsChanged(ListDataEvent e) {
30 | fireContentsChanged(this, e.getIndex0(), e.getIndex1());
31 | }
32 | };
33 | list.addListDataListener(listDataListener);
34 | }
35 |
36 | public UndoableListModel() {
37 | this(new UndoableList<>(new LinkedList<>()));
38 | }
39 |
40 | public List getList() {
41 | return Collections.unmodifiableList(list);
42 | }
43 |
44 | @Override
45 | public T getElementAt(int index) {
46 | return list.get(index);
47 | }
48 |
49 | @Override
50 | public int getSize() {
51 | return list.size();
52 | }
53 |
54 | public T get(int index) {
55 | return list.get(index);
56 | }
57 |
58 | public void add(int index, T item) {
59 | list.add(index, item);
60 | }
61 |
62 | public void clear() {
63 | int size = list.size();
64 | list.clear();
65 | }
66 |
67 | public void remove(int index) {
68 | list.remove(index);
69 | }
70 |
71 | public void beginCompoundEdit(String name) {
72 | list.beginCompoundEdit(name);
73 | }
74 |
75 | public void beginCompoundEdit(CompoundEdit edit) {
76 | list.beginCompoundEdit(edit);
77 | }
78 |
79 | public void endCompoundEdit() {
80 | list.endCompoundEdit();
81 | }
82 |
83 | public void addUndoableEditListener(UndoableEditListener listener) {
84 | list.addUndoableEditListener(listener);
85 | }
86 |
87 | public void fireContentsChanged(int index0, int index1) {
88 | fireContentsChanged(this, index0, index1);
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/src/main/java/net/sourceforge/pdfjumbler/actions/RotateAction.java:
--------------------------------------------------------------------------------
1 | package net.sourceforge.pdfjumbler.actions;
2 |
3 | import net.sourceforge.pdfjumbler.PdfJumbler;
4 | import net.sourceforge.pdfjumbler.jdragdroplist.JDragDropList;
5 | import net.sourceforge.pdfjumbler.jdragdroplist.UndoableListModel;
6 | import net.sourceforge.pdfjumbler.pdf.Page;
7 |
8 | import javax.swing.*;
9 | import javax.swing.undo.AbstractUndoableEdit;
10 | import java.awt.event.ActionEvent;
11 |
12 | public abstract class RotateAction extends AbstractAction {
13 | protected final PdfJumbler parent;
14 | private final int degrees;
15 |
16 | public RotateAction(String name, ImageIcon icon, PdfJumbler parent, int degrees) {
17 | super(name, icon);
18 | this.parent = parent;
19 | this.degrees = degrees;
20 | }
21 |
22 | @SuppressWarnings("unchecked")
23 | @Override
24 | public void actionPerformed(ActionEvent e) {
25 | JDragDropList list;
26 | if (e.getSource() instanceof JDragDropList) {
27 | list = (JDragDropList)e.getSource();
28 | } else {
29 | list = parent.getMainPdfList();
30 | }
31 | UndoableListModel model = list.getModel();
32 | int[] selected = list.getSelectedIndices();
33 |
34 | RotateEdit edit = new RotateEdit(model, selected, degrees);
35 | edit.redo();
36 |
37 | parent.getUndoManager().addEdit(edit);
38 | }
39 |
40 | private static class RotateEdit extends AbstractUndoableEdit {
41 | private final UndoableListModel listModel;
42 | private final int[] indices;
43 | private final int degrees;
44 |
45 | public RotateEdit(UndoableListModel listModel, int[] indices, int degrees) {
46 | this.listModel = listModel;
47 | this.indices = indices;
48 | this.degrees = degrees;
49 | }
50 |
51 | @Override
52 | public void undo() {
53 | for (int i : indices) {
54 | Page page = listModel.get(i);
55 | page.setRotation((page.getRotation() + 360 - degrees) % 360);
56 | listModel.fireContentsChanged(i, i);
57 | }
58 | }
59 |
60 | @Override
61 | public void redo() {
62 | for (int i : indices) {
63 | Page page = listModel.get(i);
64 | page.setRotation((page.getRotation() + degrees) % 360);
65 | listModel.fireContentsChanged(i, i);
66 | }
67 | }
68 |
69 | @Override
70 | public String toString() {
71 | return String.format("Rotate %s degrees", degrees);
72 | }
73 |
74 | @Override
75 | public String getPresentationName() {
76 | return toString();
77 | }
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/main/java/net/sourceforge/pdfjumbler/pdf/Page.java:
--------------------------------------------------------------------------------
1 | package net.sourceforge.pdfjumbler.pdf;
2 |
3 | import java.io.File;
4 | import java.util.ResourceBundle;
5 |
6 | import net.sourceforge.pdfjumbler.i18n.I18nKeys;
7 | import net.sourceforge.pdfjumbler.i18n.PdfJumblerResources;
8 |
9 | /**
10 | * Represents a page from a PDF file.
11 | *
12 | * @author Martin Gropp
13 | */
14 | public final class Page {
15 | private static final ResourceBundle resources = ResourceBundle.getBundle(PdfJumblerResources.class.getCanonicalName());
16 |
17 | private File file;
18 | private int index;
19 | private int rotation = 0;
20 |
21 | public Page(File file, int index) {
22 | this.file = file;
23 | this.index = index;
24 | }
25 |
26 | /**
27 | * Get the file this page originates from.
28 | *
29 | * @return
30 | * the page's source file
31 | */
32 | public File getFile() {
33 | return file;
34 | }
35 |
36 | /**
37 | * Set the file this page originates from.
38 | *
39 | * @param file
40 | * the page's source file
41 | */
42 | public void setFile(File file) {
43 | this.file = file;
44 | }
45 |
46 | /**
47 | * Return the page number w.r.t. the original file.
48 | *
49 | * @return
50 | * the original page number (0 based)
51 | */
52 | public int getIndex() {
53 | return index;
54 | }
55 |
56 | /**
57 | * Set the page number w.r.t. the original file.
58 | *
59 | * @param index
60 | * the original page number (0 based)
61 | */
62 | public void setIndex(int index) {
63 | this.index = index;
64 | }
65 |
66 | /**
67 | * Get the rotation angle.
68 | *
69 | * @return
70 | * the rotation angle in degrees
71 | */
72 | public int getRotation() {
73 | return rotation;
74 | }
75 |
76 | /**
77 | * Set the rotation angle.
78 | * Supported values: 0, 90, 180, 270 (degrees)
79 | *
80 | * @param rotation
81 | * the rotation angle in degrees
82 | */
83 | public void setRotation(int rotation) {
84 | this.rotation = rotation;
85 | }
86 |
87 | @Override
88 | public final boolean equals(Object o) {
89 | if (!(o instanceof Page)) {
90 | return false;
91 | }
92 |
93 | return
94 | file.equals(((Page)o).file) &&
95 | (index == ((Page)o).index) &&
96 | (rotation == ((Page)o).rotation);
97 | }
98 |
99 | @Override
100 | public final int hashCode() {
101 | return 32 * (32 * file.hashCode() + index) + rotation;
102 | }
103 |
104 | @Override
105 | public String toString() {
106 | return String.format(resources.getString(I18nKeys.PDF_PAGE_TITLE), index + 1, file.getName());
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/src/main/java/net/sourceforge/pdfjumbler/jdragdroplist/DropUtil.java:
--------------------------------------------------------------------------------
1 | package net.sourceforge.pdfjumbler.jdragdroplist;
2 |
3 | import org.tinylog.Logger;
4 |
5 | import java.awt.datatransfer.DataFlavor;
6 | import java.awt.datatransfer.UnsupportedFlavorException;
7 | import java.io.File;
8 | import java.io.IOException;
9 | import java.net.URI;
10 | import java.net.URISyntaxException;
11 | import java.util.ArrayList;
12 | import java.util.List;
13 |
14 | import javax.swing.TransferHandler.TransferSupport;
15 |
16 | /**
17 | * @author Martin Gropp
18 | */
19 | public class DropUtil {
20 | public final static DataFlavor DATA_FLAVOR_URILIST_STRING;
21 |
22 | static {
23 | try {
24 | DATA_FLAVOR_URILIST_STRING = new DataFlavor("text/uri-list;class=java.lang.String");
25 | }
26 | catch (ClassNotFoundException e) {
27 | throw new AssertionError(e);
28 | }
29 | }
30 |
31 | public static boolean isListItemDrop(TransferSupport info) {
32 | return info.isDataFlavorSupported(JDDLTransferData.DATA_FLAVOR);
33 | }
34 |
35 | public static boolean isURIDrop(TransferSupport info) {
36 | return
37 | info.isDataFlavorSupported(DataFlavor.javaFileListFlavor) ||
38 | info.isDataFlavorSupported(DATA_FLAVOR_URILIST_STRING);
39 | }
40 |
41 | @SuppressWarnings("unchecked")
42 | public static List getURIs(TransferSupport info) {
43 | if (info.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
44 | List data;
45 | try {
46 | data = (List)info.getTransferable().getTransferData(DataFlavor.javaFileListFlavor);
47 | }
48 | catch (UnsupportedFlavorException e) {
49 | throw new AssertionError(e);
50 | }
51 | catch (IOException e) {
52 | throw new RuntimeException(e);
53 | }
54 |
55 | List uriList = new ArrayList<>(data.size());
56 | for (File file : data) {
57 | uriList.add(file.toURI());
58 | }
59 |
60 | return uriList;
61 | } else if (info.isDataFlavorSupported(DATA_FLAVOR_URILIST_STRING)) {
62 | String data;
63 | try {
64 | data = (String)info.getTransferable().getTransferData(DATA_FLAVOR_URILIST_STRING);
65 | }
66 | catch (UnsupportedFlavorException e) {
67 | throw new AssertionError(e);
68 | }
69 | catch (IOException e) {
70 | throw new RuntimeException(e);
71 | }
72 |
73 | String[] uris = data.split("\n");
74 | List uriList = new ArrayList<>(uris.length);
75 |
76 | for (String uri : uris) {
77 | uri = uri.trim();
78 | if (uri.length() == 0) {
79 | continue;
80 | }
81 |
82 | try {
83 | uriList.add(new URI(uri));
84 | }
85 | catch (URISyntaxException e) {
86 | Logger.error(e);
87 | }
88 | }
89 |
90 | return uriList;
91 | }
92 |
93 | return null;
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/src/main/java/net/sourceforge/pdfjumbler/actions/DocSaveAction.java:
--------------------------------------------------------------------------------
1 | package net.sourceforge.pdfjumbler.actions;
2 |
3 | import net.sourceforge.pdfjumbler.Icons;
4 | import net.sourceforge.pdfjumbler.PdfJumbler;
5 | import net.sourceforge.pdfjumbler.PdfList;
6 | import net.sourceforge.pdfjumbler.i18n.I18nKeys;
7 | import net.sourceforge.pdfjumbler.i18n.PdfJumblerResources;
8 |
9 | import javax.swing.*;
10 | import javax.swing.filechooser.FileFilter;
11 | import java.awt.event.ActionEvent;
12 | import java.io.File;
13 |
14 | public class DocSaveAction extends AbstractAction {
15 | private static final long serialVersionUID = -1652454884847492822L;
16 | private static String lastSavedLocation = null;
17 | private final PdfJumbler parent;
18 | private final PdfList list;
19 |
20 | public DocSaveAction(PdfJumbler parent, PdfList list) {
21 | this(parent, list, Icons.DOCUMENT_SAVE);
22 | }
23 |
24 | public DocSaveAction(PdfJumbler parent, PdfList list, Icon icon) {
25 | super(PdfJumblerResources.getResources().getString(I18nKeys.SAVE_DOCUMENT), icon);
26 | this.parent = parent;
27 | this.list = list;
28 | putValue(Action.SHORT_DESCRIPTION, PdfJumblerResources.getResources().getString(I18nKeys.SAVE_DOCUMENT));
29 | putValue(Action.ACCELERATOR_KEY, PdfJumblerResources.getResources().getObject(I18nKeys.ACCELERATOR_SAVE));
30 | }
31 |
32 | @Override
33 | public void actionPerformed(ActionEvent e) {
34 | JFileChooser chooser = new JFileChooser(DocSaveAction.lastSavedLocation);
35 | chooser.setFileFilter(new FileFilter() {
36 | @Override
37 | public boolean accept(File file) {
38 | return
39 | file.isDirectory() ||
40 | ((file.getParentFile() != null) && file.getParentFile().exists()) &&
41 | (
42 | (!file.exists() ||
43 | file.isFile()) && file.canWrite() && file.getName().toLowerCase().endsWith(".pdf")
44 | );
45 | }
46 |
47 | @Override
48 | public String getDescription() {
49 | return PdfJumblerResources.getResources().getString(I18nKeys.FILE_FILTER_PDF);
50 | }
51 | });
52 |
53 | if (chooser.showSaveDialog(parent) == JFileChooser.APPROVE_OPTION) {
54 | File file = chooser.getSelectedFile();
55 | if (!file.getName().toLowerCase().endsWith(".pdf")) {
56 | file = new File(file.getAbsolutePath() + ".pdf");
57 | }
58 | if (
59 | file.exists() &&
60 | (JOptionPane.showConfirmDialog(
61 | parent,
62 | String.format(PdfJumblerResources.getResources().getString(I18nKeys.OVERWRITE_FILE_TEXT), file.getAbsolutePath()),
63 | PdfJumblerResources.getResources().getString(I18nKeys.OVERWRITE_FILE_TITLE),
64 | JOptionPane.OK_CANCEL_OPTION,
65 | JOptionPane.WARNING_MESSAGE
66 | ) != JOptionPane.OK_OPTION)
67 | ) {
68 | return;
69 | }
70 | DocSaveAction.lastSavedLocation = file.getParent();
71 | PdfJumbler.saveFile(list, file);
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | PdfJumbler
2 | ==========
3 |
4 | [](https://travis-ci.org/mgropp/pdfjumbler)
5 |
6 | PdfJumbler simple tool to rearrange, merge, delete, and rotate pages from PDF files.
7 |
8 | Releases
9 | --------
10 |
11 | * v0.4.0:
12 | * https://github.com/mgropp/pdfjumbler/releases/download/v0.4.0/pdfjumbler.jar
13 | * https://github.com/mgropp/pdfjumbler/releases/download/v0.4.0/setup-pdfjumbler.exe
14 | * v0.3.0:
15 | * https://github.com/mgropp/pdfjumbler/releases/download/v0.3.0/pdfjumbler.jar
16 | * https://github.com/mgropp/pdfjumbler/releases/download/v0.3.0/setup-pdfjumbler.exe
17 | * v0.2.0: https://github.com/mgropp/pdfjumbler/releases/download/v0.2.0/pdfjumbler.jar
18 |
19 | Installation
20 | ------------
21 | PdfJumbler requires a Java Runtime Environment (JRE), version 11 or above.
22 | You can download one at .
23 |
24 | Windows users can simply download and run the installer (setup-pdfjumbler.exe),
25 | which creates a start menu entry for PdfJumbler.
26 |
27 | For Ubuntu (and probably Debian), you can use this command to install PdfJumbler:
28 | ```
29 | curl -L https://raw.githubusercontent.com/mgropp/pdfjumbler/master/setup/ubuntu/install.sh | bash
30 | ```
31 |
32 | Alternatively, there is a runnable jar file (just double-click), pdfjumbler.jar.
33 |
34 |
35 | User Interface
36 | --------------
37 |
38 | 
39 |
40 | The user interface is actually rather simple:
41 | pages can be moved around using drag&drop (or the
42 | keyboard, see below), and for more complex operations
43 | there's a second page list available to the left
44 | (just pull it out).
45 |
46 |
47 | Keyboard Shortcuts
48 | ------------------
49 | * Ctrl+O Open file
50 | * Ctrl+S Save file
51 | * \+ Zoom in
52 | * \- Zoom out
53 | * Alt+Up Move page up
54 | * Alt+Down Move page down
55 | * Del Delete page
56 | * Ctrl+Z Undo
57 | * Ctrl+Y Redo
58 | * Ctrl+R Rotate clockwise
59 | * Ctrl+Shift+R Rotate counter-clockwise
60 |
61 |
62 | Command Line
63 | ------------
64 | PdfJumbler accepts pdf files as command line arguments.
65 |
66 | Several settings can be changed using Java system properties:
67 |
68 | * `pdfjumbler.editor`: sets the editor plugin (if installed; previous plugins are no longer supported)
69 | * PDFBox: `net.sourceforge.pdfjumbler.pdfbox.PdfEditor`
70 |
71 | * `pdfjumbler.renderer`: sets the renderer plugin (if installed; previous plugins are no longer supported)
72 | * PDFBox: `net.sourceforge.pdfjumbler.pdfbox.PdfRenderer`
73 |
74 | * `user.language`: sets the program language (in case auto-detection doesn't work).
75 | So far, `de` (German), `es` (Spanish), `ru` (Russian) and `en` (English; default)
76 | localizations are available.
77 |
78 | ### Example ###
79 | ```
80 | java -Duser.language=es -jar pdfjumbler.jar foo.pdf bar.pdf
81 | ```
82 |
--------------------------------------------------------------------------------
/src/main/java/net/sourceforge/pdfjumbler/Actions.java:
--------------------------------------------------------------------------------
1 | package net.sourceforge.pdfjumbler;
2 |
3 | import net.sourceforge.pdfjumbler.actions.*;
4 |
5 | import javax.swing.*;
6 |
7 | /**
8 | * Actions & workers for the main PdfJumbler class.
9 | *
10 | * @author Martin Gropp
11 | */
12 | public class Actions {
13 | private final Action docOpenAction;
14 | private final Action docSaveAction;
15 | private final Action docOpenAction2;
16 | private final Action docSaveAction2;
17 | private final Action undoAction;
18 | private final Action redoAction;
19 | private final Action zoomOutAction;
20 | private final Action zoomInAction;
21 | private final Action clearAction;
22 | private final Action delAction;
23 | private final Action rotateCwAction;
24 | private final Action rotateCcwAction;
25 | private final Action moveUpAction;
26 | private final Action moveDownAction;
27 | private final Action viewListAction;
28 | private final Action viewThumbnailsAction;
29 |
30 | public Actions(PdfJumbler parent) {
31 | docOpenAction = new DocOpenAction(parent, parent.getMainPdfList());
32 | docSaveAction = new DocSaveAction(parent, parent.getMainPdfList());
33 | docOpenAction2 = new DocOpenAction(parent, parent.getSecondaryPdfList(), Icons.Size16.DOCUMENT_OPEN);
34 | docSaveAction2 = new DocSaveAction(parent, parent.getSecondaryPdfList(), Icons.Size16.DOCUMENT_SAVE);
35 | undoAction = new UndoAction(parent.getUndoManager());
36 | redoAction = new RedoAction(parent.getUndoManager());
37 | zoomOutAction = new ZoomOutAction(parent);
38 | zoomInAction = new ZoomInAction(parent);
39 | clearAction = new ClearAction(parent);
40 | delAction = new DelAction(parent);
41 | rotateCwAction = new RotateClockwiseAction(parent);
42 | rotateCcwAction = new RotateCounterClockwiseAction(parent);
43 | moveUpAction = new MoveUpAction();
44 | moveDownAction = new MoveDownAction();
45 | viewListAction = new ViewListAction(parent);
46 | viewThumbnailsAction = new ViewThumbnailsAction(parent);
47 | }
48 |
49 | public Action getUndoAction() {
50 | return undoAction;
51 | }
52 |
53 | public Action getRedoAction() {
54 | return redoAction;
55 | }
56 |
57 | public Action getRotateClockwiseAction() {
58 | return rotateCwAction;
59 | }
60 |
61 | public Action getRotateCounterClockwiseAction() {
62 | return rotateCcwAction;
63 | }
64 |
65 | public Action getClearAction() {
66 | return clearAction;
67 | }
68 |
69 | public Action getDeleteAction() {
70 | return delAction;
71 | }
72 |
73 | public Action getDocOpenAction() {
74 | return docOpenAction;
75 | }
76 |
77 | public Action getDocSaveAction() {
78 | return docSaveAction;
79 | }
80 |
81 | public Action getDocOpenAction2() {
82 | return docOpenAction2;
83 | }
84 |
85 | public Action getDocSaveAction2() {
86 | return docSaveAction2;
87 | }
88 |
89 | public Action getZoomOutAction() {
90 | return zoomOutAction;
91 | }
92 |
93 | public Action getZoomInAction() {
94 | return zoomInAction;
95 | }
96 |
97 | public Action getMoveUpAction() {
98 | return moveUpAction;
99 | }
100 |
101 | public Action getMoveDownAction() {
102 | return moveDownAction;
103 | }
104 |
105 | public Action getViewListAction() {
106 | return viewListAction;
107 | }
108 |
109 | public Action getViewThumbnailsAction() {
110 | return viewThumbnailsAction;
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/src/main/java/net/sourceforge/pdfjumbler/Icons.java:
--------------------------------------------------------------------------------
1 | package net.sourceforge.pdfjumbler;
2 |
3 | import org.apache.batik.transcoder.TranscoderException;
4 | import org.apache.batik.transcoder.TranscoderInput;
5 | import org.apache.batik.transcoder.TranscoderOutput;
6 | import org.apache.batik.transcoder.TranscodingHints;
7 | import org.apache.batik.transcoder.image.ImageTranscoder;
8 | import javax.swing.ImageIcon;
9 | import java.awt.Image;
10 | import java.awt.image.BaseMultiResolutionImage;
11 | import java.awt.image.BufferedImage;
12 | import java.io.IOException;
13 | import java.io.InputStream;
14 | import java.util.List;
15 | import java.util.ArrayList;
16 |
17 | public final class Icons {
18 | private static final String BASE_DIR = "icons/";
19 |
20 | private static final int STANDARD_BASE_SIZE = 22;
21 | private static final int SMALL_BASE_SIZE = 16;
22 | private static final int APP_ICON_BASE_SIZE = 64;
23 | private static final double[] FACTORS = { 1.0, 1.25, 1.5, 1.75, 2.0, 2.25, 2.5, 2.75, 3.0 };
24 |
25 | public static final ImageIcon PDF_JUMBLER = createMultiResIcon("pdfjumbler.svg", APP_ICON_BASE_SIZE);
26 |
27 | public static final ImageIcon ZOOM_IN = createMultiResIcon("zoom-in.svg", STANDARD_BASE_SIZE);
28 | public static final ImageIcon ZOOM_OUT = createMultiResIcon("zoom-out.svg", STANDARD_BASE_SIZE);
29 | public static final ImageIcon DOCUMENT_OPEN = createMultiResIcon("document-open.svg", STANDARD_BASE_SIZE);
30 | public static final ImageIcon DOCUMENT_SAVE = createMultiResIcon("document-save.svg", STANDARD_BASE_SIZE);
31 | public static final ImageIcon EDIT_DELETE = createMultiResIcon("edit-delete.svg", STANDARD_BASE_SIZE);
32 | public static final ImageIcon MENU = createMultiResIcon("menu.svg", STANDARD_BASE_SIZE);
33 | public static final ImageIcon ROTATE_CW = createMultiResIcon("rotate-cw.svg", STANDARD_BASE_SIZE);
34 | public static final ImageIcon ROTATE_CCW = createMultiResIcon("rotate-ccw.svg", STANDARD_BASE_SIZE);
35 |
36 | public interface Size16 {
37 | ImageIcon DOCUMENT_OPEN = createMultiResIcon("document-open.svg", SMALL_BASE_SIZE);
38 | ImageIcon DOCUMENT_SAVE = createMultiResIcon("document-save.svg", SMALL_BASE_SIZE);
39 | }
40 |
41 | private static ImageIcon createMultiResIcon(String svgResourceName, int baseSize) {
42 | svgResourceName = BASE_DIR + svgResourceName;
43 |
44 | List images = new ArrayList<>(FACTORS.length);
45 | ImageTranscoder transcoder = new ImageTranscoder() {
46 | @Override
47 | public BufferedImage createImage(int width, int height) {
48 | return new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
49 | }
50 |
51 | @Override
52 | public void writeImage(BufferedImage image, TranscoderOutput output) {
53 | images.add(image);
54 | }
55 | };
56 |
57 | TranscodingHints hints = transcoder.getTranscodingHints();
58 | for (double factor : FACTORS) {
59 | hints.put(
60 | ImageTranscoder.KEY_WIDTH,
61 | (float)(factor * baseSize)
62 | );
63 |
64 | try (InputStream stream = Icons.class.getClassLoader().getResourceAsStream(svgResourceName)) {
65 | if (stream == null) {
66 | throw new IOException("Resource not found: " + svgResourceName);
67 | }
68 | TranscoderInput input = new TranscoderInput(stream);
69 | transcoder.setTranscodingHints(hints);
70 | transcoder.transcode(input, null);
71 | }
72 | catch (IOException | TranscoderException e) {
73 | throw new RuntimeException(e);
74 | }
75 | }
76 |
77 | return new ImageIcon(new BaseMultiResolutionImage(images.toArray(new Image[0])));
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/main/java/net/sourceforge/pdfjumbler/actions/MenuAction.java:
--------------------------------------------------------------------------------
1 | package net.sourceforge.pdfjumbler.actions;
2 |
3 | import net.sourceforge.pdfjumbler.Icons;
4 | import net.sourceforge.pdfjumbler.PdfJumbler;
5 | import net.sourceforge.pdfjumbler.i18n.I18nKeys;
6 | import net.sourceforge.pdfjumbler.i18n.PdfJumblerResources;
7 | import net.sourceforge.pdfjumbler.pdf.PdfEditor;
8 | import net.sourceforge.pdfjumbler.pdf.PdfProcessingFactory;
9 | import net.sourceforge.pdfjumbler.pdf.PdfRenderer;
10 |
11 | import javax.swing.*;
12 | import java.awt.*;
13 | import java.awt.event.ActionEvent;
14 |
15 | public class MenuAction extends AbstractAction {
16 | private static final long serialVersionUID = -3880011502734901446L;
17 | private final PdfJumbler parent;
18 | public JPopupMenu menu = new JPopupMenu();
19 | private Component component = null;
20 |
21 | public MenuAction(PdfJumbler parent) {
22 | super(PdfJumblerResources.getResources().getString(I18nKeys.MENU), Icons.MENU);
23 | this.parent = parent;
24 | putValue(Action.SHORT_DESCRIPTION, PdfJumblerResources.getResources().getString(I18nKeys.MENU));
25 | populate();
26 | }
27 |
28 | private void populate() {
29 | menu.add(parent.getActions().getUndoAction());
30 | menu.add(parent.getActions().getRedoAction());
31 |
32 | menu.addSeparator();
33 |
34 | menu.add(parent.getActions().getRotateClockwiseAction());
35 | menu.add(parent.getActions().getRotateCounterClockwiseAction());
36 |
37 | menu.addSeparator();
38 |
39 | menu.add(parent.getActions().getDeleteAction());
40 | menu.add(parent.getActions().getClearAction());
41 |
42 | menu.addSeparator();
43 |
44 | ButtonGroup viewGroup = new ButtonGroup();
45 | JMenu viewMenu = new JMenu(PdfJumblerResources.getResources().getString(I18nKeys.MENU_VIEW));
46 |
47 | JRadioButtonMenuItem itemViewList = new JRadioButtonMenuItem(parent.getActions().getViewListAction());
48 | itemViewList.setSelected(parent.getMainPdfList().getShowCellText());
49 | viewGroup.add(itemViewList);
50 | viewMenu.add(itemViewList);
51 |
52 | JRadioButtonMenuItem itemViewThumbnails = new JRadioButtonMenuItem(parent.getActions().getViewThumbnailsAction());
53 | itemViewThumbnails.setSelected(!parent.getMainPdfList().getShowCellText());
54 | viewGroup.add(itemViewThumbnails);
55 | viewMenu.add(itemViewThumbnails);
56 |
57 | menu.add(viewMenu);
58 |
59 | menu.addSeparator();
60 |
61 | ButtonGroup editorGroup = new ButtonGroup();
62 | JMenu editorMenu = new JMenu(PdfJumblerResources.getResources().getString(I18nKeys.MENU_EDITOR));
63 | for (Class extends PdfEditor> cls : PdfProcessingFactory.getAvailableEditors()) {
64 | JRadioButtonMenuItem item = new JRadioButtonMenuItem(new ChangeEditorAction(parent, cls));
65 | editorGroup.add(item);
66 | editorMenu.add(item);
67 | if (PdfProcessingFactory.getEditorClass().equals(cls)) {
68 | item.setSelected(true);
69 | }
70 | }
71 | menu.add(editorMenu);
72 |
73 | ButtonGroup rendererGroup = new ButtonGroup();
74 | JMenu rendererMenu = new JMenu(PdfJumblerResources.getResources().getString(I18nKeys.MENU_RENDERER));
75 | for (Class extends PdfRenderer> cls : PdfProcessingFactory.getAvailableRenderers()) {
76 | JRadioButtonMenuItem item = new JRadioButtonMenuItem(new ChangeRendererAction(parent, cls));
77 | rendererGroup.add(item);
78 | rendererMenu.add(item);
79 | if (PdfProcessingFactory.getRendererClass().equals(cls)) {
80 | item.setSelected(true);
81 | }
82 | }
83 | menu.add(rendererMenu);
84 |
85 | menu.addSeparator();
86 | menu.add(new AboutAction(parent));
87 | }
88 |
89 | public void setComponent(Component c) {
90 | this.component = c;
91 | }
92 |
93 | @Override
94 | public void actionPerformed(ActionEvent e) {
95 | menu.show(component, 0, component.getHeight());
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/src/main/java/net/sourceforge/pdfjumbler/i18n/PdfJumblerResources_de.java:
--------------------------------------------------------------------------------
1 | package net.sourceforge.pdfjumbler.i18n;
2 |
3 | import java.util.ListResourceBundle;
4 |
5 | /**
6 | * @author Martin Gropp
7 | */
8 | public class PdfJumblerResources_de extends ListResourceBundle implements I18nKeys {
9 | @Override
10 | protected Object[][] getContents() {
11 | return new Object[][] {
12 | { OPEN_DOCUMENT, "Dokument öffnen/hinzufügen" },
13 | { SAVE_DOCUMENT, "Dokument speichern" },
14 | { FILE_FILTER_PDF, "PDF-Dateien (*.pdf)" },
15 | { OVERWRITE_FILE_TITLE, "Überschreiben?" },
16 | { OVERWRITE_FILE_TEXT, "Die Datei %s existiert bereits. Überschreiben?" },
17 | { ZOOM_OUT, "Verkleinern" },
18 | { ZOOM_IN, "Vergrößern" },
19 | { CLEAR_LIST, "Alle löschen" },
20 | { DELETE, "Löschen" },
21 | { ABOUT, "Informationen" },
22 | { LIST_DROP_PDFS_TO_EDIT, "Ziehen Sie PDF-Dateien hierher\num sie zu bearbeiten!" },
23 | { LIST_CLIPBOARD_EMPTY, "Ablage\n(leer)" },
24 | { CONFIRM_CLEAR_LIST_TITLE, "Löschen bestätigen" },
25 | { CONFIRM_CLEAR_LIST_TEXT, "Alles löschen?" },
26 | { NO_PDF_EDITOR_TITLE, "Fehler" },
27 | { NO_PDF_EDITOR_TEXT, "Es konnte kein PDF-Editor-Plugin geladen werden." },
28 | { NO_PDF_RENDERER_TITLE, "Fehler" },
29 | { NO_PDF_RENDERER_TEXT, "Es konnte kein PDF-Renderer-Plugin geladen werden." },
30 | { PDF_PAGE_TITLE, "%d (%s)" },
31 | { MENU, "Menü" },
32 | { MENU_VIEW, "Ansicht" },
33 | { MENU_EDITOR, "PDF-Editor" },
34 | { MENU_RENDERER, "PDF-Anzeige" },
35 | { PROGRESS_ABORT, "Abbrechen" },
36 | { UNDO, "Rückgängig" },
37 | { REDO, "Wiederherstellen" },
38 | { ROTATE_CW, "Drehen (im Uhrzeigersinn)" },
39 | { ROTATE_CCW, "Drehen (gegen den Uhrzeigersinn)" },
40 | { ABOUT_TITLE, "Informationen" },
41 | { PLUGIN_ERROR_TITLE, "Plugin-Fehler" },
42 | { PLUGIN_INIT_ERROR, "Fehler beim Initialisieren von Plugin %s" },
43 | { VIEW_LIST, "Liste" },
44 | { VIEW_THUMBNAILS, "Nur Miniaturansichten" },
45 | {
46 | PLUGIN_ERROR_VERSION_INCOMPATIBLE,
47 | "Dieses Plugin benötigt PdfJumbler Version %s."
48 | },
49 | {
50 | ABOUT_TEXT,
51 | "PdfJumbler %s \nCopyright (C) 2021 Martin Gropp\n" +
52 | "\n" +
53 | "PDF Editor: %s\n" +
54 | "PDF Renderer: %s\n" +
55 | "\n" +
56 | "Dieses Programm ist freie Software. Sie können es unter den Bedingungen\n" +
57 | "der GNU General Public License, wie von der Free Software Foundation\n" +
58 | "veröffentlicht, weitergeben und/oder modifizieren, entweder gemäß\n" +
59 | "Version 3 der Lizenz oder (nach Ihrer Option) jeder späteren Version.\n\n" +
60 | "Die Veröffentlichung dieses Programms erfolgt in der Hoffnung, daß es\n" +
61 | "Ihnen von Nutzen sein wird, aber OHNE IRGENDEINE GARANTIE, sogar ohne\n" +
62 | "die implizite Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR\n" +
63 | "EINEN BESTIMMTEN ZWECK.\n" +
64 | "Details finden Sie in der GNU General Public License.\n\n" +
65 | "Sie sollten ein Exemplar der GNU General Public License zusammen mit\n" +
66 | "diesem Programm erhalten haben. Falls nicht, siehe http://www.gnu.org/licenses/.\n" +
67 | "\n" +
68 | "Dieses Programm enthält möglicherweise (unter anderem) die folgenden Inhalte:\n" +
69 | "PdfBox - Copyright Apache Foundation (Apache License)\n" +
70 | "FlatLaf - Copyright FormDev Software GmbH (Apache License)\n" +
71 | "IntelliJ IDEA CE Icons - Copyright JetBrains s.r.o. (Apache License)\n" +
72 | "Ubuntu Mobile Icons - Copyright Canonical Ltd. (CC-BY-SA-3.0)"
73 | }
74 | };
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/main/java/net/sourceforge/pdfjumbler/i18n/PdfJumblerResources_ru.java:
--------------------------------------------------------------------------------
1 | package net.sourceforge.pdfjumbler.i18n;
2 |
3 | import java.util.ListResourceBundle;
4 |
5 | /**
6 | * @author s-r-grass
7 | */
8 | public class PdfJumblerResources_ru extends ListResourceBundle implements I18nKeys {
9 | @Override
10 | protected Object[][] getContents() {
11 | return new Object[][] {
12 | { OPEN_DOCUMENT, "Открыть/Добавить" },
13 | { SAVE_DOCUMENT, "Сохранить" },
14 | { FILE_FILTER_PDF, "PDF документы (*.pdf)" },
15 | { OVERWRITE_FILE_TITLE, "Перезаписать?" },
16 | { OVERWRITE_FILE_TEXT, "Файл %s уже существует. Перезаписать?" },
17 | { ZOOM_OUT, "Уменьшить" },
18 | { ZOOM_IN, "Увеличить" },
19 | { CLEAR_LIST, "Очистить список" },
20 | { DELETE, "Удалить" },
21 | { ABOUT, "О программе" },
22 | { LIST_DROP_PDFS_TO_EDIT, "Перетащите сюда PDF-документы!" },
23 | { LIST_CLIPBOARD_EMPTY, "Буфер обмена \n (пусто)" },
24 | { CONFIRM_CLEAR_LIST_TITLE, "Подтвердить" },
25 | { CONFIRM_CLEAR_LIST_TEXT, "Удалить все страницы?" },
26 | { NO_PDF_EDITOR_TITLE, "Ошибка" },
27 | { NO_PDF_EDITOR_TEXT, "Не удалось загрузить плагин редактора PDF." },
28 | { NO_PDF_RENDERER_TITLE, "Ошибка" },
29 | { NO_PDF_RENDERER_TEXT, "Не удалось загрузить плагин для рендеринга PDF." },
30 | { PDF_PAGE_TITLE, "%d (%s)" },
31 | { MENU, "Настройки" },
32 | { MENU_VIEW, "Вид" },
33 | { MENU_EDITOR, "PDF Редактор" },
34 | { MENU_RENDERER, "PDF Рендер" },
35 | { MOVE_UP, "Переместить вверх" },
36 | { MOVE_DOWN, "Переместить вниз" },
37 | { PROGRESS_ABORT, "Прервать" },
38 | { UNDO, "Отменить" },
39 | { REDO, "Повторить" },
40 | { ROTATE_CW, "Повернуть по часовой стрелке" },
41 | { ROTATE_CCW, "Повернуть против часовой стрелки" },
42 | { ABOUT_TITLE, "О программе" },
43 | { PLUGIN_ERROR_TITLE, "Ошибка плагина" },
44 | { PLUGIN_INIT_ERROR, "Ошибка инициализации плагина %s" },
45 | { VIEW_LIST, "Ведомость" },
46 | { VIEW_THUMBNAILS, "Эскиз" },
47 | {
48 | PLUGIN_ERROR_VERSION_INCOMPATIBLE,
49 | "Для этого плагина требуется версия PdfJumbler %s."
50 | },
51 | {
52 | ABOUT_TEXT,
53 | "PdfJumbler %s \nАвторские права (C) 2021 Мартин Гропп\n" +
54 | "Перевод: s-r-grass\n" +
55 | "\n" +
56 | "PDF Редактор: %s\n" +
57 | "PDF Рендер: %s\n" +
58 | "\n" +
59 | "Эта программа - свободное ПО; вы можете распространять и/или изменять\n" +
60 | "в соответствии с условиями Стандартной общественной лицензии GNU, опубликованной\n" +
61 | "Фондом Свободного Программного обеспечения; либо версии 3 Лицензии, либо (на ваш\n" +
62 | "выбор) любой более поздней версии.\n\n" +
63 | "Эта программа распространяется в надежде, что она будет полезна, но\n" +
64 | "БЕЗ КАКИХ-ЛИБО ГАРАНТИЙ; даже без подразумеваемой гарантии\n" +
65 | "КОММЕРЧЕСКОЙ ЦЕННОСТИ или ПРИГОДНОСТИ ДЛЯ ОПРЕДЕЛЕННОЙ ЦЕЛИ.\n" +
66 | "Для подробностей смотрите GNU General Public License.\n\n" +
67 | "Вы должны были получить копию Стандаplртной общественной лицензии GNU вместе с\n" +
68 | "с этой программой; если нет, смотри http://www.gnu.org/licenses/.\n" +
69 | "\n" +
70 | "Это программное обеспечение может включать (среди прочего) следующие сторонние программы:\n" +
71 | "PdfBox - Авторские права Apache Foundation (Apache license)\n" +
72 | "FlatLaf - Авторские права FormDev Software GmbH (Apache License)\n" +
73 | "IntelliJ IDEA CE Icons - Авторские права JetBrains s.r.o. (Apache License)\n" +
74 | "Ubuntu Mobile Icons - Авторские права Canonical Ltd. (CC-BY-SA-3.0)"
75 | }
76 | };
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/main/java/net/sourceforge/pdfjumbler/i18n/PdfJumblerResources_es.java:
--------------------------------------------------------------------------------
1 | package net.sourceforge.pdfjumbler.i18n;
2 |
3 | import java.util.ListResourceBundle;
4 |
5 | /**
6 | * @author Andres Yesid Diaz Pinto
7 | */
8 | public class PdfJumblerResources_es extends ListResourceBundle implements I18nKeys {
9 | @Override
10 | protected Object[][] getContents() {
11 | return new Object[][] {
12 | { OPEN_DOCUMENT, "Abrir/añadir documento" },
13 | { SAVE_DOCUMENT, "Guardar documento" },
14 | { FILE_FILTER_PDF, "Documentos PDF (*.pdf)" },
15 | { OVERWRITE_FILE_TITLE, "¿Sobreescribir?" },
16 | { OVERWRITE_FILE_TEXT, "El fichero ya existe, ¿lo desea sobreescribir?" },
17 | { ZOOM_OUT, "Zoom out" },
18 | { ZOOM_IN, "Zoom in" },
19 | { CLEAR_LIST, "Borrar lista" },
20 | { DELETE, "Eliminar" },
21 | { ABOUT, "Acerca de" },
22 | { LIST_DROP_PDFS_TO_EDIT, "¡Arrastrar documentos PDF aquí!" },
23 | { LIST_CLIPBOARD_EMPTY, "Portapapeles\n(vacío)" },
24 | { CONFIRM_CLEAR_LIST_TITLE, "Confirmar" },
25 | { CONFIRM_CLEAR_LIST_TEXT, "¿Eliminar todas las páginas?" },
26 | { NO_PDF_EDITOR_TITLE, "Error" },
27 | { NO_PDF_EDITOR_TEXT, "Error al cargar el plugin para editar PDF." },
28 | { NO_PDF_RENDERER_TITLE, "Error" },
29 | { NO_PDF_RENDERER_TEXT, "Error al cargar el plugin para mostrar PDF." },
30 | { PDF_PAGE_TITLE, "%d (%s)" },
31 | { MENU, "Configuración" },
32 | { MENU_VIEW, "Ver" },
33 | { MENU_EDITOR, "Plugin para editar PDF" },
34 | { MENU_RENDERER, "Plugin para mostrar PDF" },
35 | { MOVE_UP, "Mover arriba" },
36 | { MOVE_DOWN, "Mover abajo" },
37 | { PROGRESS_ABORT, "Detener" },
38 | { UNDO, "Deshacer" },
39 | { REDO, "Rehacer" },
40 | { ROTATE_CW, "Girar en sentido horario" },
41 | { ROTATE_CCW, "Girar en sentido antihorario" },
42 | { ABOUT_TITLE, "Acerca de" },
43 | { PLUGIN_ERROR_TITLE, "Error Plugin" },
44 | { PLUGIN_INIT_ERROR, "Error al inicializar el plugin %s" },
45 | { VIEW_LIST, "Lista" },
46 | { VIEW_THUMBNAILS, "Sólo miniaturas" },
47 | {
48 | PLUGIN_ERROR_VERSION_INCOMPATIBLE,
49 | "Este plugin requiere la versión %s de PdfJumbler."
50 | },
51 | {
52 | ABOUT_TEXT,
53 | "PdfJumbler %s \nCopyright (C) 2021 Martin Gropp\n" +
54 | "Traducción: Andres Yesid Diaz Pinto\n" +
55 | "\n" +
56 | "PDF Editor: %s\n" +
57 | "PDF Renderer: %s\n" +
58 | "\n" +
59 | "This program is free software; you can redistribute it and/or modify it\n" +
60 | "under the terms of the GNU General Public License as published by the\n" +
61 | "Free Software Foundation; either version 3 of the License, or (at your\n" +
62 | "option) any later version.\n\n" +
63 | "This program is distributed in the hope that it will be useful, but\n" +
64 | "WITHOUT ANY WARRANTY; without even the implied warranty of\n" +
65 | "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n" +
66 | "See the GNU General Public License for more details.\n\n" +
67 | "You should have received a copy of the GNU General Public License along\n" +
68 | "with this program; if not, see http://www.gnu.org/licenses/.\n" +
69 | "\n" +
70 | "Este programa puede incluir, entre otros, las siguientes partes de software de terceros:\n" +
71 | "PdfBox - Copyright Apache Foundation (Apache license)\n" +
72 | "FlatLaf - Copyright FormDev Software GmbH (Apache License)\n" +
73 | "IntelliJ IDEA CE Icons - Copyright JetBrains s.r.o. (Apache License)\n" +
74 | "Ubuntu Mobile Icons - Copyright Canonical Ltd. (CC-BY-SA-3.0)"
75 | }
76 | };
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/main/java/net/sourceforge/pdfjumbler/pdf/Plugin.java:
--------------------------------------------------------------------------------
1 | package net.sourceforge.pdfjumbler.pdf;
2 |
3 | import java.io.File;
4 | import java.io.IOException;
5 | import java.io.InputStream;
6 | import java.net.MalformedURLException;
7 | import java.net.URL;
8 | import java.net.URLClassLoader;
9 | import java.util.ArrayList;
10 | import java.util.Collections;
11 | import java.util.List;
12 | import java.util.Properties;
13 | import java.util.ResourceBundle;
14 | import java.util.jar.JarFile;
15 | import java.util.zip.ZipEntry;
16 |
17 | import net.sourceforge.pdfjumbler.PdfJumbler;
18 | import net.sourceforge.pdfjumbler.i18n.I18nKeys;
19 | import net.sourceforge.pdfjumbler.i18n.PdfJumblerResources;
20 |
21 | /**
22 | * @author Martin Gropp
23 | */
24 | public class Plugin {
25 | public static final String REQUIRED_VERSION_KEY = "pdfjumbler-required-version";
26 | public static final String PDF_EDITOR_KEY = "pdfjumbler-plugin-editor";
27 | public static final String PDF_RENDERER_KEY = "pdfjumbler-plugin-renderer";
28 |
29 | private static final ResourceBundle resources = ResourceBundle.getBundle(PdfJumblerResources.class.getCanonicalName());
30 |
31 | private final List> pdfEditorClasses;
32 | private final List> pdfRendererClasses;
33 |
34 | @SuppressWarnings({ "rawtypes", "unchecked" })
35 | public Plugin(File jarFile) throws IOException, PluginException {
36 | Properties properties = new Properties();
37 | try (JarFile jar = new JarFile(jarFile)) {
38 | ZipEntry entry = jar.getEntry("pdfjumbler-plugin.properties");
39 | if (entry == null) {
40 | throw new IOException("Missing pdfjumbler-plugin.properties entry!");
41 | }
42 |
43 | try (InputStream stream = jar.getInputStream(entry)) {
44 | properties.load(stream);
45 | }
46 | }
47 |
48 | String pdfEditorClassNames = properties.getProperty(PDF_EDITOR_KEY);
49 | String pdfRendererClassNames = properties.getProperty(PDF_RENDERER_KEY);
50 | if (pdfEditorClassNames == null && pdfRendererClassNames == null) {
51 | throw new IOException("No plugins found in jar!");
52 | }
53 |
54 | String requiredVersion = properties.getProperty(REQUIRED_VERSION_KEY);
55 | if (requiredVersion == null) {
56 | throw new PluginException("Missing version declaration in plugin!");
57 | }
58 | if (Integer.parseInt(requiredVersion) > PdfJumbler.VERSION) {
59 | throw new PluginException(String.format(
60 | resources.getString(I18nKeys.PLUGIN_ERROR_VERSION_INCOMPATIBLE),
61 | requiredVersion
62 | ));
63 | }
64 |
65 | ClassLoader classLoader;
66 | try {
67 | classLoader = new URLClassLoader(
68 | new URL[]{ jarFile.toURI().toURL() },
69 | Plugin.class.getClassLoader()
70 | );
71 | }
72 | catch (MalformedURLException e) {
73 | // This should not happen...
74 | throw new AssertionError(e);
75 | }
76 |
77 | if (pdfEditorClassNames != null) {
78 | String[] classNames = pdfEditorClassNames.split(",");
79 | pdfEditorClasses = new ArrayList<>(classNames.length);
80 | for (String className : classNames) {
81 | try {
82 | pdfEditorClasses.add(
83 | (Class) classLoader.loadClass(
84 | className
85 | )
86 | );
87 | }
88 | catch (Exception e) {
89 | throw new PluginException(
90 | "Failed to load editor class from plugin!",
91 | e
92 | );
93 | }
94 | }
95 | } else {
96 | pdfEditorClasses = null;
97 | }
98 |
99 | if (pdfRendererClassNames != null) {
100 | String[] classNames = pdfRendererClassNames.split(",");
101 | pdfRendererClasses = new ArrayList<>(classNames.length);
102 | for (String className : classNames) {
103 | try {
104 | pdfRendererClasses.add(
105 | (Class) classLoader.loadClass(
106 | className
107 | )
108 | );
109 | }
110 | catch (Exception e) {
111 | throw new PluginException(
112 | "Failed to load renderer class from plugin!",
113 | e
114 | );
115 | }
116 | }
117 | } else {
118 | pdfRendererClasses = null;
119 | }
120 | }
121 |
122 | public boolean hasPdfEditors() {
123 | return (pdfEditorClasses != null && !pdfEditorClasses.isEmpty());
124 | }
125 |
126 | public boolean hasPdfRenderers() {
127 | return (pdfRendererClasses != null && !pdfRendererClasses.isEmpty());
128 | }
129 |
130 | public List> getPdfEditorClasses() {
131 | return Collections.unmodifiableList(pdfEditorClasses);
132 | }
133 |
134 | public List> getPdfRendererClasses() {
135 | return Collections.unmodifiableList(pdfRendererClasses);
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/src/main/java/net/sourceforge/pdfjumbler/PdfCellRenderer.java:
--------------------------------------------------------------------------------
1 | package net.sourceforge.pdfjumbler;
2 |
3 | import java.awt.Color;
4 | import java.awt.Component;
5 | import java.awt.Graphics;
6 | import java.awt.Graphics2D;
7 | import java.awt.Image;
8 | import java.awt.RenderingHints;
9 | import java.io.IOException;
10 | import java.util.HashMap;
11 | import java.util.Map;
12 |
13 | import javax.swing.DefaultListCellRenderer;
14 | import javax.swing.JList;
15 |
16 | import net.sourceforge.pdfjumbler.pdf.Page;
17 | import net.sourceforge.pdfjumbler.pdf.PdfEditor;
18 | import net.sourceforge.pdfjumbler.pdf.PdfProcessingFactory;
19 | import net.sourceforge.pdfjumbler.pdf.PdfProcessorListener;
20 | import net.sourceforge.pdfjumbler.pdf.PdfRenderer;
21 |
22 | /**
23 | * @author Martin Gropp
24 | */
25 | public class PdfCellRenderer extends DefaultListCellRenderer {
26 | private static final long serialVersionUID = 2395480480996785313L;
27 |
28 | private final Map imageCache = new HashMap<>();
29 | private PdfRenderer renderer;
30 |
31 | private Page page = null;
32 | private final int padding = 6;
33 | private final int textWidth = 128;
34 |
35 | private int thumbnailWidth = 96;
36 | private int thumbnailHeight = 128;
37 |
38 | private boolean showText;
39 |
40 | public PdfCellRenderer(PdfRenderer renderer) {
41 | this(renderer, true);
42 | }
43 |
44 | public PdfCellRenderer(PdfRenderer renderer, boolean showText) {
45 | this.showText = showText;
46 | this.renderer = PdfProcessingFactory.getRenderer();
47 | PdfProcessingFactory.addProcessorListener(
48 | new PdfProcessorListener() {
49 | @Override
50 | public void pdfEditorChanged(PdfEditor oldEditor, PdfEditor newEditor) { }
51 |
52 | @Override
53 | public void pdfRendererChanged(PdfRenderer oldRenderer, PdfRenderer newRenderer) {
54 | PdfCellRenderer.this.renderer = newRenderer;
55 | imageCache.clear();
56 | }
57 | }
58 | );
59 |
60 | sizeChanged();
61 | }
62 |
63 | private void sizeChanged() {
64 | int width = thumbnailWidth + 2 * padding;
65 | int height = thumbnailHeight + 2 * padding;
66 | if (showText) {
67 | width += textWidth + 2 * padding;
68 | }
69 |
70 | setSize(width, height);
71 | setPreferredSize(getSize());
72 | clearImageCache();
73 | }
74 |
75 | @Override
76 | public void paint(Graphics g) {
77 | ((Graphics2D)g).setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
78 | super.paint(g);
79 |
80 | if (page == null) {
81 | return;
82 | }
83 |
84 | Image cachedImage;
85 |
86 | if (imageCache.containsKey(page)) {
87 | cachedImage = imageCache.get(page);
88 | } else {
89 | try {
90 | cachedImage = renderer.renderPage(page, thumbnailWidth, thumbnailHeight);
91 | }
92 | catch (IOException e) {
93 | throw new RuntimeException(e);
94 | }
95 | imageCache.put(page, cachedImage);
96 | }
97 |
98 | g.drawImage(cachedImage, padding, padding, null);
99 | g.setColor(Color.LIGHT_GRAY);
100 | g.drawRect(padding, padding, cachedImage.getWidth(null), cachedImage.getHeight(null));
101 |
102 | if (showText) {
103 | g.setColor(this.getForeground());
104 | g.drawString(
105 | page.toString(),
106 | thumbnailWidth + 2 * padding,
107 | padding + g.getFontMetrics().getAscent()
108 | );
109 | }
110 | }
111 |
112 | @Override
113 | public Component getListCellRendererComponent(JList> list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
114 | PdfCellRenderer component = (PdfCellRenderer)super.getListCellRendererComponent(
115 | list,
116 | null,
117 | index,
118 | isSelected,
119 | cellHasFocus
120 | );
121 | assert (component == this);
122 | component.page = (Page)value;
123 | component.setLocation(0, getHeight() * index);
124 | return component;
125 | }
126 |
127 | public void setThumbnailWidth(int thumbnailWidth) {
128 | if (thumbnailWidth != this.thumbnailWidth) {
129 | this.thumbnailWidth = thumbnailWidth;
130 | sizeChanged();
131 | }
132 | }
133 |
134 | public int getThumbnailWidth() {
135 | return thumbnailWidth;
136 | }
137 |
138 | public void setThumbnailHeight(int thumbnailHeight) {
139 | if (thumbnailHeight != this.thumbnailHeight) {
140 | this.thumbnailHeight = thumbnailHeight;
141 | sizeChanged();
142 | }
143 | }
144 |
145 | public int getThumbnailHeight() {
146 | return thumbnailHeight;
147 | }
148 |
149 | public void clearImageCache() {
150 | imageCache.clear();
151 | }
152 |
153 | public boolean getShowText() {
154 | return showText;
155 | }
156 |
157 | /**
158 | * Display the page's text representation next to the thumbnail iff true.
159 | */
160 | public void setShowText(boolean value) {
161 | this.showText = value;
162 | sizeChanged();
163 | }
164 | }
165 |
--------------------------------------------------------------------------------
/src/main/java/net/sourceforge/pdfjumbler/pdfbox/PdfRenderer.java:
--------------------------------------------------------------------------------
1 | package net.sourceforge.pdfjumbler.pdfbox;
2 |
3 | import net.sourceforge.pdfjumbler.pdf.Page;
4 | import org.apache.pdfbox.pdmodel.PDDocument;
5 | import org.apache.pdfbox.pdmodel.PDPage;
6 | import org.apache.pdfbox.pdmodel.common.PDRectangle;
7 | import org.apache.pdfbox.rendering.PDFRenderer;
8 |
9 | import java.awt.*;
10 | import java.awt.image.BufferedImage;
11 | import java.io.File;
12 | import java.io.IOException;
13 | import java.util.HashMap;
14 | import java.util.Iterator;
15 | import java.util.Map;
16 |
17 | /**
18 | * PdfJumbler interface to PDFBox.
19 | *
20 | * @author Martin Gropp
21 | */
22 | public class PdfRenderer implements net.sourceforge.pdfjumbler.pdf.PdfRenderer {
23 | public static String getFriendlyName() {
24 | return "PDFBox";
25 | }
26 |
27 | private final Map docMap = new HashMap<>();
28 | private final Map> pageMap = new HashMap<>();
29 |
30 | private PDDocument openDocument(File file) throws IOException {
31 | PDDocument pdDoc = PDDocument.load(file);
32 | if (pdDoc == null) {
33 | throw new IOException("Failed to load PDF document.");
34 | }
35 |
36 | docMap.put(file, pdDoc);
37 | pageMap.put(file, new HashMap<>());
38 |
39 | return pdDoc;
40 | }
41 |
42 | private PDDocument getDocument(File file) throws IOException {
43 | PDDocument pdDoc = docMap.get(file);
44 | if (pdDoc != null) {
45 | return pdDoc;
46 | } else {
47 | return openDocument(file);
48 | }
49 | }
50 |
51 | private PDPage getPDPage(Page page) throws IOException {
52 | PDDocument pdDoc = docMap.get(page.getFile());
53 | if (pdDoc == null) {
54 | pdDoc = openDocument(page.getFile());
55 | }
56 |
57 | Map map = pageMap.get(page.getFile());
58 | assert (map != null);
59 |
60 | PDPage pdPage;
61 | if (map.containsKey(page)) {
62 | pdPage = map.get(page);
63 | } else {
64 | pdPage = pdDoc.getPage(page.getIndex());
65 | map.put(page, pdPage);
66 | }
67 |
68 | return pdPage;
69 | }
70 |
71 | private static BufferedImage rotate(BufferedImage srcImage, int degrees) {
72 | // TODO: this is not fast
73 |
74 | if (degrees == 0) {
75 | return srcImage;
76 | } else if (degrees == 90 || degrees == 180 || degrees == 270) {
77 | int srcWidth = srcImage.getWidth();
78 | int srcHeight = srcImage.getHeight();
79 |
80 | BufferedImage dstImage;
81 | if (degrees == 180) {
82 | dstImage = new BufferedImage(srcWidth, srcHeight, srcImage.getType());
83 |
84 | for (int x = 0; x < srcWidth; x++) {
85 | for (int y = 0; y < srcHeight; y++) {
86 | dstImage.setRGB(
87 | srcWidth - x - 1,
88 | srcHeight - y - 1,
89 | srcImage.getRGB(x, y)
90 | );
91 | }
92 | }
93 | } else {
94 | dstImage = new BufferedImage(srcHeight, srcWidth, srcImage.getType());
95 |
96 | if (degrees == 90) {
97 | for (int x = 0; x < srcWidth; x++) {
98 | for (int y = 0; y < srcHeight; y++) {
99 | dstImage.setRGB(
100 | srcHeight - y - 1,
101 | x,
102 | srcImage.getRGB(x, y)
103 | );
104 | }
105 | }
106 | } else {
107 | for (int x = 0; x < srcWidth; x++) {
108 | for (int y = 0; y < srcHeight; y++) {
109 | dstImage.setRGB(
110 | y,
111 | srcWidth - x - 1,
112 | srcImage.getRGB(x, y)
113 | );
114 | }
115 | }
116 | }
117 | }
118 |
119 | return dstImage;
120 | } else {
121 | throw new IllegalArgumentException("Unsupported rotation: " + degrees);
122 | }
123 | }
124 |
125 | @Override
126 | public Image renderPage(Page page, int maxWidth, int maxHeight) throws IOException {
127 | PDPage pdPage = getPDPage(page);
128 | PDRectangle cropBox = pdPage.getCropBox();
129 |
130 | double ax;
131 | double ay;
132 |
133 | int degrees = page.getRotation();
134 | if (degrees == 0 || degrees == 180) {
135 | ax = maxWidth / cropBox.getWidth();
136 | ay = maxHeight / cropBox.getHeight();
137 | } else if (degrees == 90 || degrees == 270) {
138 | ax = maxWidth / cropBox.getHeight();
139 | ay = maxHeight / cropBox.getWidth();
140 | } else {
141 | throw new IllegalArgumentException("Unsupported rotation: " + degrees);
142 | }
143 |
144 | PDFRenderer renderer = new PDFRenderer(getDocument(page.getFile()));
145 | BufferedImage image = renderer.renderImage(page.getIndex(), (float)Math.min(ax, ay));
146 |
147 | return rotate(image, degrees);
148 | }
149 |
150 | @Override
151 | public int getNumberOfPages(File file) throws IOException {
152 | return getDocument(file).getNumberOfPages();
153 | }
154 |
155 | @Override
156 | public void close() throws IOException {
157 | Iterator> it = docMap.entrySet().iterator();
158 | while (it.hasNext()) {
159 | Map.Entry entry = it.next();
160 | entry.getValue().close();
161 | pageMap.remove(entry.getKey());
162 | it.remove();
163 | }
164 | }
165 | }
--------------------------------------------------------------------------------
/src/main/java/net/sourceforge/pdfjumbler/i18n/PdfJumblerResources.java:
--------------------------------------------------------------------------------
1 | package net.sourceforge.pdfjumbler.i18n;
2 |
3 | import java.awt.event.InputEvent;
4 | import java.awt.event.KeyEvent;
5 | import java.util.ListResourceBundle;
6 | import java.util.ResourceBundle;
7 |
8 | import javax.swing.KeyStroke;
9 |
10 | /**
11 | * @author Martin Gropp
12 | */
13 | public class PdfJumblerResources extends ListResourceBundle implements I18nKeys {
14 | private static final ResourceBundle resources = ResourceBundle.getBundle(PdfJumblerResources.class.getCanonicalName());
15 |
16 | public static ResourceBundle getResources() {
17 | return resources;
18 | }
19 |
20 | @Override
21 | protected Object[][] getContents() {
22 | return new Object[][] {
23 | { OPEN_DOCUMENT, "Open/add document" },
24 | { SAVE_DOCUMENT, "Save document" },
25 | { FILE_FILTER_PDF, "PDF documents (*.pdf)" },
26 | { OVERWRITE_FILE_TITLE, "Overwrite?" },
27 | { OVERWRITE_FILE_TEXT, "The file %s already exists. Overwrite?" },
28 | { ZOOM_OUT, "Zoom out" },
29 | { ZOOM_IN, "Zoom in" },
30 | { CLEAR_LIST, "Clear list" },
31 | { DELETE, "Delete" },
32 | { ABOUT, "About" },
33 | { LIST_DROP_PDFS_TO_EDIT, "Drag PDF documents here!" },
34 | { LIST_CLIPBOARD_EMPTY, "Clipboard\n(empty)" },
35 | { CONFIRM_CLEAR_LIST_TITLE, "Confirm" },
36 | { CONFIRM_CLEAR_LIST_TEXT, "Delete all pages?" },
37 | { NO_PDF_EDITOR_TITLE, "Error" },
38 | { NO_PDF_EDITOR_TEXT, "Failed to load a PDF editor plugin." },
39 | { NO_PDF_RENDERER_TITLE, "Error" },
40 | { NO_PDF_RENDERER_TEXT, "Failed to load a PDF renderer plugin." },
41 | { PDF_PAGE_TITLE, "%d (%s)" },
42 | { MENU, "Menu" },
43 | { MENU_VIEW, "View" },
44 | { MENU_EDITOR, "PDF Editor" },
45 | { MENU_RENDERER, "PDF Renderer" },
46 | { MOVE_UP, "Move up" },
47 | { MOVE_DOWN, "Move down" },
48 | { PROGRESS_ABORT, "Abort" },
49 | { UNDO, "Undo" },
50 | { REDO, "Redo" },
51 | { ROTATE_CW, "Rotate clockwise" },
52 | { ROTATE_CCW, "Rotate counter-clockwise" },
53 | { ACCELERATOR_OPEN, KeyStroke.getKeyStroke('O', I18nUtil.MENU_SHORTCUT_KEY) },
54 | { ACCELERATOR_SAVE, KeyStroke.getKeyStroke('S', I18nUtil.MENU_SHORTCUT_KEY) },
55 | { ACCELERATOR_ZOOM_IN, KeyStroke.getKeyStroke('+') },
56 | { ACCELERATOR_ZOOM_OUT, KeyStroke.getKeyStroke('-') },
57 | { ACCELERATOR_MOVE_UP, KeyStroke.getKeyStroke(KeyEvent.VK_UP, InputEvent.ALT_DOWN_MASK) },
58 | { ACCELERATOR_MOVE_DOWN, KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, InputEvent.ALT_DOWN_MASK) },
59 | { ACCELERATOR_DELETE, KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0) },
60 | { ACCELERATOR_UNDO, KeyStroke.getKeyStroke('Z', I18nUtil.MENU_SHORTCUT_KEY) },
61 | { ACCELERATOR_REDO, KeyStroke.getKeyStroke('Y', I18nUtil.MENU_SHORTCUT_KEY) },
62 | { ACCELERATOR_ROTATE_CW, KeyStroke.getKeyStroke('R', I18nUtil.MENU_SHORTCUT_KEY) },
63 | { ACCELERATOR_ROTATE_CCW, KeyStroke.getKeyStroke('R', I18nUtil.MENU_SHORTCUT_KEY | InputEvent.SHIFT_DOWN_MASK) },
64 | { ABOUT_TITLE, "About" },
65 | { PLUGIN_ERROR_TITLE, "Plugin Error" },
66 | { PLUGIN_INIT_ERROR, "Error initializing plugin %s" },
67 | { VIEW_LIST, "List" },
68 | { VIEW_THUMBNAILS, "Thumbnails only" },
69 | {
70 | PLUGIN_ERROR_VERSION_INCOMPATIBLE,
71 | "This plugin requires PdfJumbler version %s."
72 | },
73 | {
74 | ABOUT_TEXT,
75 | "PdfJumbler %s \nCopyright (C) 2021 Martin Gropp\n" +
76 | "\n" +
77 | "PDF Editor: %s\n" +
78 | "PDF Renderer: %s\n" +
79 | "\n" +
80 | "This program is free software; you can redistribute it and/or modify it\n" +
81 | "under the terms of the GNU General Public License as published by the\n" +
82 | "Free Software Foundation; either version 3 of the License, or (at your\n" +
83 | "option) any later version.\n\n" +
84 | "This program is distributed in the hope that it will be useful, but\n" +
85 | "WITHOUT ANY WARRANTY; without even the implied warranty of\n" +
86 | "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n" +
87 | "See the GNU General Public License for more details.\n\n" +
88 | "You should have received a copy of the GNU General Public License along\n" +
89 | "with this program; if not, see http://www.gnu.org/licenses/.\n" +
90 | "\n" +
91 | "This software may include (among others) the following third party content:\n" +
92 | "PdfBox - Copyright Apache Foundation (Apache License)\n" +
93 | "FlatLaf - Copyright FormDev Software GmbH (Apache License)\n" +
94 | "IntelliJ IDEA CE Icons - Copyright JetBrains s.r.o. (Apache License)\n" +
95 | "Ubuntu Mobile Icons - Copyright Canonical Ltd. (CC-BY-SA-3.0)"
96 | }
97 | };
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/src/main/java/net/sourceforge/pdfjumbler/ProgressDialog.java:
--------------------------------------------------------------------------------
1 | package net.sourceforge.pdfjumbler;
2 |
3 | import java.awt.Frame;
4 | import java.awt.GridBagConstraints;
5 | import java.awt.GridBagLayout;
6 | import java.awt.Insets;
7 | import java.awt.event.ActionEvent;
8 | import java.awt.event.WindowAdapter;
9 | import java.awt.event.WindowEvent;
10 | import java.beans.PropertyChangeEvent;
11 | import java.beans.PropertyChangeListener;
12 | import java.util.ResourceBundle;
13 | import java.util.concurrent.CancellationException;
14 | import java.util.concurrent.ExecutionException;
15 |
16 | import javax.swing.AbstractAction;
17 | import javax.swing.JButton;
18 | import javax.swing.JDialog;
19 | import javax.swing.JLabel;
20 | import javax.swing.JProgressBar;
21 | import javax.swing.SwingUtilities;
22 | import javax.swing.SwingWorker;
23 |
24 | import net.sourceforge.pdfjumbler.i18n.I18nKeys;
25 | import net.sourceforge.pdfjumbler.i18n.PdfJumblerResources;
26 |
27 | /**
28 | * @author Martin Gropp
29 | */
30 | public class ProgressDialog extends JDialog {
31 | private static final long serialVersionUID = 5790932640733135316L;
32 | private static final ResourceBundle resources = ResourceBundle.getBundle(PdfJumblerResources.class.getCanonicalName());
33 |
34 | private final SwingWorker,?> worker;
35 | private final JProgressBar progressBar;
36 | private final JLabel statusLabel;
37 |
38 | public ProgressDialog(Frame parent, SwingWorker,?> worker) {
39 | super(parent, true);
40 | this.worker = worker;
41 |
42 | setSize(300, 120);
43 | if (parent != null) {
44 | setLocationRelativeTo(null);
45 | setLocation(
46 | parent.getLocationOnScreen().x + (parent.getWidth() - getWidth()) / 2,
47 | parent.getLocationOnScreen().y + (parent.getHeight() - getHeight()) / 2
48 | );
49 | }
50 |
51 | setLayout(new GridBagLayout());
52 | GridBagConstraints c = new GridBagConstraints();
53 | c.fill = GridBagConstraints.HORIZONTAL;
54 | c.gridwidth = GridBagConstraints.REMAINDER;
55 | c.weightx = 1.0;
56 | c.weighty = 0.0;
57 | c.insets = new Insets(4, 4, 4, 4);
58 |
59 | statusLabel = new JLabel();
60 | add(statusLabel, c);
61 |
62 | progressBar = new JProgressBar(JProgressBar.HORIZONTAL, 0, 100);
63 | progressBar.setIndeterminate(true);
64 | progressBar.setEnabled(true);
65 | add(progressBar, c);
66 |
67 | JButton abortButton = new JButton(
68 | new AbstractAction(resources.getString(I18nKeys.PROGRESS_ABORT)) {
69 | private static final long serialVersionUID = -8203887045883710945L;
70 |
71 | @Override
72 | public void actionPerformed(ActionEvent e) {
73 | abort();
74 | }
75 | }
76 | );
77 | c.fill = GridBagConstraints.NONE;
78 | add(abortButton, c);
79 |
80 | setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
81 | addWindowListener(
82 | new WindowAdapter() {
83 | public void windowClosing(WindowEvent e) {
84 | abort();
85 | }
86 | }
87 | );
88 |
89 | worker.addPropertyChangeListener(
90 | new PropertyChangeListener() {
91 | @Override
92 | public void propertyChange(final PropertyChangeEvent e) {
93 | if (e.getPropertyName().equals("state") && e.getNewValue().equals(SwingWorker.StateValue.DONE)) {
94 | SwingUtilities.invokeLater(
95 | new Runnable() {
96 | @Override
97 | public void run() {
98 | ProgressDialog.this.setVisible(false);
99 | ProgressDialog.this.dispose();
100 | }
101 | }
102 | );
103 | } else if (e.getPropertyName().equals("note")) {
104 | SwingUtilities.invokeLater(
105 | new Runnable() {
106 | @Override
107 | public void run() {
108 | statusLabel.setText((String)e.getNewValue());
109 | }
110 | }
111 | );
112 | } else if (e.getPropertyName().equals("progress")) {
113 | SwingUtilities.invokeLater(
114 | new Runnable() {
115 | @Override
116 | public void run() {
117 | if ((Integer)e.getNewValue() >= 0) {
118 | progressBar.setIndeterminate(false);
119 | progressBar.setValue((Integer)e.getNewValue());
120 | }
121 | }
122 | }
123 | );
124 | }
125 | }
126 | }
127 | );
128 | }
129 |
130 | public void abort() {
131 | if (!worker.isDone()) {
132 | worker.cancel(true);
133 | }
134 | setVisible(false);
135 | dispose();
136 | }
137 |
138 | public static boolean run(SwingWorker,?> worker, Frame parent) throws Exception {
139 | ProgressDialog dialog = new ProgressDialog(parent, worker);
140 | worker.execute();
141 | dialog.setVisible(true);
142 | try {
143 | worker.get();
144 | }
145 | catch (ExecutionException e) {
146 | if (e.getCause() instanceof CancellationException) {
147 | return false;
148 | } else if (e.getCause() instanceof Exception) {
149 | throw (Exception)e.getCause();
150 | } else {
151 | // ?!?
152 | throw new AssertionError(e);
153 | }
154 | }
155 |
156 | return !worker.isCancelled();
157 | }
158 | }
--------------------------------------------------------------------------------
/src/main/java/net/sourceforge/pdfjumbler/PdfList.java:
--------------------------------------------------------------------------------
1 | package net.sourceforge.pdfjumbler;
2 |
3 | import java.awt.Color;
4 | import java.awt.FontMetrics;
5 | import java.awt.Graphics;
6 | import java.awt.Graphics2D;
7 | import java.awt.RenderingHints;
8 | import java.awt.event.MouseWheelEvent;
9 | import java.awt.event.MouseWheelListener;
10 | import java.io.File;
11 | import java.net.URI;
12 | import java.util.ArrayList;
13 | import java.util.Objects;
14 | import java.util.prefs.Preferences;
15 |
16 | import javax.swing.JList;
17 | import javax.swing.TransferHandler.TransferSupport;
18 |
19 | import net.sourceforge.pdfjumbler.jdragdroplist.*;
20 | import net.sourceforge.pdfjumbler.pdf.Page;
21 | import net.sourceforge.pdfjumbler.pdf.PdfProcessingFactory;
22 |
23 | /**
24 | * @author Martin Gropp
25 | */
26 | public class PdfList extends JDragDropList {
27 | private static final long serialVersionUID = 7475943073466784769L;
28 |
29 | private static final int DEFAULT_VISIBLE_ROW_COUNT = 8;
30 |
31 | private String displayMessage = null;
32 |
33 | public PdfList(boolean showText) {
34 | super(new UndoableListModel<>());
35 | setDropListener(new URIDropListener());
36 | addMouseWheelListener(new ZoomMouseWheelListener());
37 |
38 | setCellRenderer(new PdfCellRenderer(
39 | PdfProcessingFactory.getRenderer(),
40 | showText
41 | ));
42 | this.setShowCellText(showText);
43 | }
44 |
45 | private void drawCenteredText(Graphics g, String message, boolean alignCenter) {
46 | String[] lines = message.split("\n");
47 |
48 | FontMetrics fm = g.getFontMetrics();
49 | int height = lines.length * fm.getHeight();
50 | int width = 0;
51 | if (!alignCenter) {
52 | for (String line : lines) {
53 | if (fm.stringWidth(line) > width) {
54 | width = fm.stringWidth(line);
55 | }
56 | }
57 | }
58 |
59 | for (int i = 0; i < lines.length; i++) {
60 | g.drawString(
61 | lines[i],
62 | (getWidth() - (alignCenter ? fm.stringWidth(lines[i]) : width)) / 2,
63 | (getHeight() - height) / 2 + i*fm.getHeight()
64 | );
65 | }
66 | }
67 |
68 | @Override
69 | public void paint(Graphics g) {
70 | ((Graphics2D)g).setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
71 | super.paint(g);
72 |
73 | if (getDisplayMessage() != null) {
74 | if (getModel().getSize() == 0) {
75 | g.setColor(Color.LIGHT_GRAY);
76 | drawCenteredText(g, getDisplayMessage(), true);
77 | }
78 | }
79 | }
80 |
81 | public int getThumbnailSize() {
82 | if (!(getCellRenderer() instanceof PdfCellRenderer)) {
83 | throw new IllegalStateException("Wrong cell renderer in use.");
84 | }
85 |
86 | return ((PdfCellRenderer)getCellRenderer()).getThumbnailWidth();
87 | }
88 |
89 | public void setThumbnailSize(int size) {
90 | if (!(getCellRenderer() instanceof PdfCellRenderer)) {
91 | throw new IllegalStateException("Wrong cell renderer in use.");
92 | }
93 | if (size < 0) {
94 | throw new IllegalArgumentException(Integer.toString(size));
95 | }
96 |
97 | // TODO: Aspect ratio of first?/most? pages.
98 | ((PdfCellRenderer)getCellRenderer()).setThumbnailWidth(size);
99 | ((PdfCellRenderer)getCellRenderer()).setThumbnailHeight((int)(size * Math.sqrt(2)));
100 | updateUI();
101 | }
102 |
103 | public boolean getShowCellText() {
104 | if (!(getCellRenderer() instanceof PdfCellRenderer)) {
105 | throw new IllegalStateException("Wrong cell renderer in use.");
106 | }
107 |
108 | return ((PdfCellRenderer)getCellRenderer()).getShowText();
109 | }
110 |
111 | public void setShowCellText(boolean value) {
112 | if (!(getCellRenderer() instanceof PdfCellRenderer)) {
113 | throw new IllegalStateException("Wrong cell renderer in use.");
114 | }
115 |
116 | ((PdfCellRenderer)getCellRenderer()).setShowText(value);
117 |
118 | if (value) {
119 | setVisibleRowCount(DEFAULT_VISIBLE_ROW_COUNT);
120 | setLayoutOrientation(JList.VERTICAL);
121 | } else {
122 | setVisibleRowCount(-1);
123 | setLayoutOrientation(JList.HORIZONTAL_WRAP);
124 | }
125 | updateUI();
126 | }
127 |
128 | public static void removeItem(JDragDropList list, int index) {
129 | list.getModel().remove(index);
130 | }
131 |
132 | public void setDisplayMessage(String displayMessage) {
133 | this.displayMessage = displayMessage;
134 | if (getModel().getSize() == 0) {
135 | invalidate();
136 | }
137 | }
138 |
139 | public String getDisplayMessage() {
140 | return displayMessage;
141 | }
142 |
143 | private final class ZoomMouseWheelListener implements MouseWheelListener {
144 | @Override
145 | public void mouseWheelMoved(MouseWheelEvent event) {
146 | if (event.isControlDown() && (getCellRenderer() instanceof PdfCellRenderer)) {
147 | int zoomSpeed = 20;
148 | setThumbnailSize(
149 | Math.max(10, getThumbnailSize() - zoomSpeed * event.getWheelRotation())
150 | );
151 | } else if (getParent() != null) {
152 | getParent().dispatchEvent(event);
153 | }
154 | }
155 | }
156 |
157 | private final class URIDropListener implements DropListener {
158 | @Override
159 | public boolean acceptDrop(Object sender, TransferSupport info) {
160 | return
161 | (info.getTransferable().isDataFlavorSupported(JDDLTransferData.DATA_FLAVOR)) ||
162 | DropUtil.isURIDrop(info);
163 | }
164 |
165 | @Override
166 | public boolean handleDrop(Object sender, TransferSupport info) {
167 | @SuppressWarnings("unchecked")
168 | JDragDropList list = (JDragDropList)sender;
169 | if (DropUtil.isURIDrop(info)) {
170 | ArrayList files = new ArrayList<>();
171 | int position = list.getDropLocation().getIndex();
172 | for (URI uri : Objects.requireNonNull(DropUtil.getURIs(info))) {
173 | if (uri.getScheme().equals("file")) {
174 | files.add(new File(uri.getPath()));
175 | }
176 | }
177 |
178 | PdfJumbler.openFiles(PdfList.this, position, files);
179 |
180 | return true;
181 | } else {
182 | return false;
183 | }
184 | }
185 | }
186 |
187 | @Override
188 | public UndoableListModel getModel() {
189 | return super.getModel();
190 | }
191 | }
192 |
--------------------------------------------------------------------------------
/src/main/java/net/sourceforge/pdfjumbler/jdragdroplist/JDragDropList.java:
--------------------------------------------------------------------------------
1 | package net.sourceforge.pdfjumbler.jdragdroplist;
2 |
3 | import java.awt.datatransfer.DataFlavor;
4 | import java.awt.datatransfer.Transferable;
5 | import java.awt.datatransfer.UnsupportedFlavorException;
6 | import java.awt.dnd.DnDConstants;
7 | import java.io.IOException;
8 | import java.util.List;
9 |
10 | import javax.swing.DropMode;
11 | import javax.swing.JComponent;
12 | import javax.swing.JList;
13 | import javax.swing.ListModel;
14 | import javax.swing.ListSelectionModel;
15 | import javax.swing.TransferHandler;
16 | import javax.swing.undo.CompoundEdit;
17 |
18 | /**
19 | * A JList that supports drag & drop.
20 | *
21 | * @author Martin Gropp
22 | */
23 | public class JDragDropList extends JList {
24 | private static final long serialVersionUID = -6709912327369063711L;
25 |
26 | private DropListener dropListener = null;
27 |
28 | public JDragDropList(UndoableListModel model) {
29 | super(model);
30 |
31 | setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
32 | setDragEnabled(true);
33 | setDropMode(DropMode.INSERT);
34 | setTransferHandler(new JDDLTransferHandler());
35 | }
36 |
37 | public DropListener getDropListener() {
38 | return dropListener;
39 | }
40 |
41 | public void setDropListener(DropListener dropListener) {
42 | this.dropListener = dropListener;
43 | }
44 |
45 | private final class JDDLTransferHandler extends TransferHandler {
46 | private static final long serialVersionUID = 8850624713729645277L;
47 |
48 | private void copyItems(JDragDropList extends T> sourceList, JDragDropList super T> targetList, List sourceObjects, int destIndex) {
49 | UndoableListModel super T> model = targetList.getModel();
50 |
51 | model.beginCompoundEdit("Copy");
52 | try {
53 | for (T obj : sourceObjects) {
54 | model.add(destIndex++, obj);
55 | }
56 | }
57 | finally {
58 | model.endCompoundEdit();
59 | }
60 |
61 | targetList.setSelectionInterval(destIndex - sourceObjects.size(), destIndex - 1);
62 | }
63 |
64 | private void moveItems(JDragDropList extends T> sourceList, JDragDropList super T> targetList, int[] sourceIndices, int destIndex) {
65 | UndoableListModel extends T> sourceModel = sourceList.getModel();
66 | UndoableListModel super T> targetModel = targetList.getModel();
67 |
68 | CompoundEdit edit = new CompoundEdit() {
69 | private static final long serialVersionUID = -8658162613932247385L;
70 |
71 | @Override
72 | public String getPresentationName() {
73 | return "Move";
74 | }
75 | };
76 |
77 | sourceModel.beginCompoundEdit(edit);
78 | try {
79 | targetModel.beginCompoundEdit(edit);
80 | try {
81 | for (int i = 0; i < sourceIndices.length; i++) {
82 | int index = sourceIndices[i];
83 | T item = sourceModel.get(index);
84 | sourceModel.remove(index);
85 |
86 | for (int j = i + 1; j < sourceIndices.length; j++) {
87 | int value = sourceIndices[j];
88 | if (value > index) {
89 | sourceIndices[j]--;
90 | }
91 | if (sourceModel == targetModel) {
92 | if (value >= destIndex) {
93 | sourceIndices[j]++;
94 | }
95 | }
96 | }
97 | if ((sourceModel == targetModel) && (index < destIndex)) {
98 | destIndex--;
99 | }
100 |
101 | targetModel.add(destIndex, item);
102 | destIndex++;
103 | }
104 | }
105 | finally {
106 | targetModel.endCompoundEdit();
107 | }
108 | }
109 | finally {
110 | sourceModel.endCompoundEdit();
111 | }
112 |
113 | targetList.setSelectionInterval(destIndex - sourceIndices.length, destIndex - 1);
114 |
115 | sourceList.updateUI();
116 | targetList.updateUI();
117 | }
118 |
119 | @SuppressWarnings("unchecked")
120 | private JDDLTransferData getData(TransferHandler.TransferSupport info) {
121 | JDDLTransferData data;
122 | try {
123 | data = (JDDLTransferData)info.getTransferable().getTransferData(JDDLTransferData.DATA_FLAVOR);
124 | }
125 | catch (UnsupportedFlavorException | IOException e) {
126 | throw new AssertionError(e);
127 | }
128 |
129 | return data;
130 | }
131 |
132 | private boolean canImportHere(TransferHandler.TransferSupport info) {
133 | return
134 | ((info.getDropAction() & DnDConstants.ACTION_COPY_OR_MOVE) != 0) &&
135 | info.isDataFlavorSupported(JDDLTransferData.DATA_FLAVOR);
136 | }
137 |
138 | @Override
139 | public boolean canImport(TransferHandler.TransferSupport info) {
140 | if (JDragDropList.this.dropListener != null) {
141 | return JDragDropList.this.dropListener.acceptDrop(JDragDropList.this, info);
142 | } else {
143 | return canImportHere(info);
144 | }
145 | }
146 |
147 | @Override
148 | public boolean importData(TransferHandler.TransferSupport info) {
149 | if (!info.isDrop()) {
150 | return false;
151 | }
152 | if (!canImportHere(info)) {
153 | if (
154 | (JDragDropList.this.dropListener != null) &&
155 | JDragDropList.this.dropListener.acceptDrop(JDragDropList.this, info)
156 | ) {
157 | return JDragDropList.this.dropListener.handleDrop(JDragDropList.this, info);
158 | } else {
159 | return false;
160 | }
161 | }
162 |
163 | JDDLTransferData data = getData(info);
164 | int destIndex = JDragDropList.this.getDropLocation().getIndex();
165 |
166 | if ((info.getDropAction() & DnDConstants.ACTION_COPY) != 0) {
167 | copyItems(data.getSourceList(), JDragDropList.this, data.getValuesList(), destIndex);
168 | } else if ((info.getDropAction() & DnDConstants.ACTION_MOVE) != 0) {
169 | moveItems(data.getSourceList(), JDragDropList.this, data.getIndices(), destIndex);
170 | } else {
171 | return false;
172 | }
173 |
174 | return true;
175 | }
176 |
177 | @Override
178 | public int getSourceActions(JComponent c) {
179 | return DnDConstants.ACTION_COPY_OR_MOVE;
180 | //return DnDConstants.ACTION_COPY_OR_MOVE | DnDConstants.ACTION_LINK | DnDConstants.ACTION_MOVE | DnDConstants.ACTION_REFERENCE;
181 | }
182 |
183 | @Override
184 | protected Transferable createTransferable(JComponent c) {
185 | @SuppressWarnings("unchecked")
186 | final JDDLTransferData data = new JDDLTransferData<>((JDragDropList) c);
187 | return new Transferable() {
188 | @Override
189 | public JDDLTransferData getTransferData(DataFlavor flavor)
190 | throws UnsupportedFlavorException {
191 | if (!flavor.equals(JDDLTransferData.DATA_FLAVOR)) {
192 | throw new UnsupportedFlavorException(flavor);
193 | }
194 |
195 | return data;
196 | }
197 |
198 | @Override
199 | public DataFlavor[] getTransferDataFlavors() {
200 | return new DataFlavor[] { JDDLTransferData.DATA_FLAVOR };
201 | }
202 |
203 | @Override
204 | public boolean isDataFlavorSupported(DataFlavor flavor) {
205 | return flavor.equals(JDDLTransferData.DATA_FLAVOR);
206 | }
207 | };
208 | }
209 | }
210 |
211 | @Override
212 | public UndoableListModel getModel() {
213 | return (UndoableListModel)super.getModel();
214 | }
215 |
216 | @Override
217 | public void setModel(ListModel model) {
218 | if (model instanceof UndoableListModel) {
219 | super.setModel(model);
220 | } else {
221 | throw new IllegalArgumentException("Unsupported model type.");
222 | }
223 | }
224 |
225 | @Override
226 | @Deprecated
227 | @SuppressWarnings("unchecked")
228 | public T[] getSelectedValues() {
229 | return (T[])super.getSelectedValues();
230 | }
231 | }
232 |
--------------------------------------------------------------------------------
/src/main/resources/icons/pdfjumbler.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
223 |
--------------------------------------------------------------------------------
/src/main/java/net/sourceforge/pdfjumbler/PdfJumbler.java:
--------------------------------------------------------------------------------
1 | package net.sourceforge.pdfjumbler;
2 |
3 | import com.formdev.flatlaf.FlatLightLaf;
4 | import net.sourceforge.pdfjumbler.actions.MenuAction;
5 | import net.sourceforge.pdfjumbler.actions.OpenDocumentWorker;
6 | import net.sourceforge.pdfjumbler.actions.ReloadingProcessorListener;
7 | import net.sourceforge.pdfjumbler.actions.RotateClockwiseAction;
8 | import net.sourceforge.pdfjumbler.actions.SaveDocumentWorker;
9 | import net.sourceforge.pdfjumbler.i18n.I18nKeys;
10 | import net.sourceforge.pdfjumbler.i18n.PdfJumblerResources;
11 | import net.sourceforge.pdfjumbler.pdf.PdfProcessingFactory;
12 | import org.tinylog.Logger;
13 |
14 | import javax.swing.Action;
15 | import javax.swing.ActionMap;
16 | import javax.swing.Box;
17 | import javax.swing.InputMap;
18 | import javax.swing.JButton;
19 | import javax.swing.JComponent;
20 | import javax.swing.JFrame;
21 | import javax.swing.JOptionPane;
22 | import javax.swing.JPanel;
23 | import javax.swing.JScrollPane;
24 | import javax.swing.JSplitPane;
25 | import javax.swing.JToolBar;
26 | import javax.swing.KeyStroke;
27 | import javax.swing.SwingUtilities;
28 | import javax.swing.undo.UndoManager;
29 | import java.awt.BorderLayout;
30 | import java.awt.Dimension;
31 | import java.awt.dnd.DropTarget;
32 | import java.io.File;
33 | import java.lang.Thread.UncaughtExceptionHandler;
34 | import java.lang.reflect.InvocationTargetException;
35 | import java.util.ArrayList;
36 | import java.util.Collection;
37 | import java.util.ResourceBundle;
38 | import java.util.prefs.Preferences;
39 |
40 | /**
41 | * PdfJumbler main class.
42 | *
43 | * @author Martin Gropp
44 | */
45 | public class PdfJumbler extends JFrame {
46 | private static final long serialVersionUID = 4382647271800905977L;
47 | public static final int VERSION = 20210402;
48 | public static final String VERSION_STRING = "2021-04-02";
49 | private static final ResourceBundle resources = ResourceBundle.getBundle(PdfJumblerResources.class.getCanonicalName());
50 |
51 | private static PdfJumbler instance = null;
52 |
53 | private final PdfList mainList;
54 | private final PdfList secondaryList;
55 | private final UndoManager undoManager = new UniqueUndoManager();
56 | private final Actions actions;
57 |
58 | public PdfList getMainPdfList() {
59 | return mainList;
60 | }
61 |
62 | public PdfList getSecondaryPdfList() {
63 | return secondaryList;
64 | }
65 |
66 | public UndoManager getUndoManager() {
67 | return undoManager;
68 | }
69 |
70 | public Actions getActions() {
71 | return actions;
72 | }
73 |
74 | public static void openFiles(PdfList list, int insertPos, Collection files) {
75 | if (files.size() > 0) {
76 | try {
77 | ProgressDialog.run(
78 | new OpenDocumentWorker(
79 | list.getModel(),
80 | insertPos,
81 | files
82 | ),
83 | PdfJumbler.instance
84 | );
85 | }
86 | catch (Exception e) {
87 | Logger.error(e);
88 | JOptionPane.showMessageDialog(instance, e.getMessage(), e.getClass().getSimpleName(), JOptionPane.ERROR_MESSAGE);
89 | }
90 | }
91 | }
92 |
93 | static void openFiles(PdfList list, int insertPos, String... fileNames) {
94 | ArrayList files = new ArrayList<>(fileNames.length);
95 | for (String fileName : fileNames) {
96 | files.add(new File(fileName));
97 | }
98 | openFiles(list, insertPos, files);
99 | }
100 |
101 | public static void saveFile(PdfList list, File file) {
102 | try {
103 | ProgressDialog.run(
104 | new SaveDocumentWorker(
105 | list.getModel(),
106 | file
107 | ),
108 | instance
109 | );
110 | }
111 | catch (Exception e) {
112 | Logger.error(e);
113 | JOptionPane.showMessageDialog(instance, e.getMessage(), e.getClass().getSimpleName(), JOptionPane.ERROR_MESSAGE);
114 | }
115 | }
116 |
117 | public static void setLookAndFeel() {
118 | FlatLightLaf.install();
119 | }
120 |
121 | private void installExceptionHandler() {
122 | while (true) {
123 | try {
124 | SwingUtilities.invokeAndWait(
125 | new Runnable() {
126 | @Override
127 | public void run() {
128 | Thread.setDefaultUncaughtExceptionHandler(
129 | new UncaughtExceptionHandler() {
130 | @Override
131 | public void uncaughtException(Thread t, Throwable e) {
132 | Logger.error(e);
133 | JOptionPane.showMessageDialog(
134 | PdfJumbler.this,
135 | e.getLocalizedMessage(),
136 | e.getClass().getSimpleName(),
137 | JOptionPane.ERROR_MESSAGE
138 | );
139 | }
140 | }
141 | );
142 | }
143 | }
144 | );
145 | }
146 | catch (InterruptedException e) {
147 | continue;
148 | }
149 | catch (InvocationTargetException e) {
150 | Logger.error(e);
151 | JOptionPane.showMessageDialog(
152 | this,
153 | e.getLocalizedMessage(),
154 | e.getClass().getSimpleName(),
155 | JOptionPane.ERROR_MESSAGE
156 | );
157 | }
158 |
159 | break;
160 | }
161 | }
162 |
163 | private static void registerAccelerators(JComponent component, Action... actions) {
164 | InputMap inputMap = component.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
165 | ActionMap actionMap = component.getActionMap();
166 | for (Action action : actions) {
167 | inputMap.put((KeyStroke)action.getValue(Action.ACCELERATOR_KEY), action);
168 | actionMap.put(action, action);
169 | }
170 | }
171 |
172 | private PdfJumbler(String[] files) {
173 | Logger.info("PdfJumbler is starting");
174 |
175 | PdfJumbler.instance = this;
176 |
177 | installExceptionHandler();
178 | setLookAndFeel();
179 | setTitle(getClass().getSimpleName());
180 | setIconImage(Icons.PDF_JUMBLER.getImage());
181 |
182 | // Prepare toolbars
183 | JToolBar toolBar = new JToolBar();
184 | JToolBar toolBar2 = new JToolBar();
185 | toolBar.setFloatable(false);
186 | toolBar2.setFloatable(false);
187 |
188 | // Lists
189 | mainList = new PdfList(
190 | Preferences.userNodeForPackage(PdfJumbler.class).getBoolean(ConfigKeys.SHOW_TEXT, true)
191 | );
192 | mainList.setDisplayMessage(resources.getString(I18nKeys.LIST_DROP_PDFS_TO_EDIT));
193 | JScrollPane mainPane = new JScrollPane(mainList);
194 |
195 | secondaryList = new PdfList(
196 | Preferences.userNodeForPackage(PdfJumbler.class).getBoolean(ConfigKeys.SHOW_TEXT, true)
197 | );
198 | secondaryList.setDisplayMessage(resources.getString(I18nKeys.LIST_CLIPBOARD_EMPTY));
199 | secondaryList.setThumbnailSize(16);
200 | JScrollPane secondaryPane = new JScrollPane(secondaryList);
201 |
202 | JPanel secondaryPanel = new JPanel();
203 | secondaryPanel.setLayout(new BorderLayout());
204 | secondaryPanel.add(secondaryPane, BorderLayout.CENTER);
205 | secondaryPanel.add(toolBar2, BorderLayout.SOUTH);
206 |
207 | mainList.getModel().addUndoableEditListener(undoManager);
208 | secondaryList.getModel().addUndoableEditListener(undoManager);
209 |
210 | mainList.setMinimumSize(new Dimension(100, 100));
211 | mainPane.setMinimumSize(new Dimension(100, 100));
212 | secondaryList.setMinimumSize(new Dimension(0, 0));
213 | secondaryPane.setMinimumSize(new Dimension(0, 0));
214 | secondaryPanel.setMinimumSize(new Dimension(0, 0));
215 |
216 | JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, secondaryPanel, mainPane);
217 | splitPane.setOneTouchExpandable(true);
218 | splitPane.setDividerLocation(0.0);
219 | add(splitPane, BorderLayout.CENTER);
220 |
221 | // Actions
222 | actions = new Actions(this);
223 |
224 | // Toolbar
225 | toolBar.add(actions.getDocOpenAction());
226 | toolBar.add(actions.getDocSaveAction());
227 | toolBar.addSeparator();
228 | toolBar.add(actions.getZoomOutAction());
229 | toolBar.add(actions.getZoomInAction());
230 | toolBar.addSeparator();
231 | JButton delButton = toolBar.add(actions.getDeleteAction());
232 | delButton.setDropTarget(new DropTarget(delButton, new TrashDropTargetListener()));
233 | toolBar.addSeparator();
234 | toolBar.add(new RotateClockwiseAction(this));
235 |
236 | toolBar.add(Box.createHorizontalGlue());
237 | MenuAction menuAction = new MenuAction(this);
238 | menuAction.setComponent(toolBar.add(menuAction));
239 |
240 | add(toolBar, BorderLayout.NORTH);
241 |
242 | // Toolbar 2
243 | toolBar2.add(Box.createHorizontalGlue());
244 | toolBar2.add(actions.getDocOpenAction2());
245 | toolBar2.add(actions.getDocSaveAction2());
246 |
247 | // Open files
248 | openFiles(mainList, -1, files);
249 |
250 | // Listen for editor/renderer changes
251 | PdfProcessingFactory.addProcessorListener(new ReloadingProcessorListener(this));
252 |
253 | // Register accelerators
254 | registerAccelerators(
255 | getRootPane(),
256 | actions.getDocOpenAction(), actions.getDocSaveAction(),
257 | actions.getZoomInAction(), actions.getZoomOutAction(),
258 | actions.getUndoAction(), actions.getRedoAction(),
259 | actions.getRotateClockwiseAction(),
260 | actions.getRotateCounterClockwiseAction()
261 | );
262 | registerAccelerators(
263 | mainList,
264 | actions.getMoveUpAction(), actions.getMoveDownAction(), actions.getDeleteAction()
265 | );
266 | registerAccelerators(
267 | secondaryList,
268 | actions.getMoveUpAction(),
269 | actions.getMoveDownAction(),
270 | actions.getDeleteAction()
271 | );
272 | }
273 |
274 | public static void main(String[] args) {
275 | try {
276 | PdfJumbler frame = new PdfJumbler(args);
277 | frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
278 | frame.setSize(340, 600);
279 | frame.setVisible(true);
280 | }
281 | catch (Exception e) {
282 | e.printStackTrace();
283 | JOptionPane.showMessageDialog(null, e.getLocalizedMessage(), e.getClass().getSimpleName(), JOptionPane.ERROR_MESSAGE);
284 | }
285 | }
286 | }
287 |
288 |
--------------------------------------------------------------------------------
/src/main/java/net/sourceforge/pdfjumbler/pdf/PdfProcessingFactory.java:
--------------------------------------------------------------------------------
1 | package net.sourceforge.pdfjumbler.pdf;
2 |
3 | import java.io.File;
4 | import java.io.IOException;
5 | import java.lang.reflect.InvocationTargetException;
6 | import java.lang.reflect.Method;
7 | import java.net.URL;
8 | import java.util.ArrayList;
9 | import java.util.Arrays;
10 | import java.util.Collection;
11 | import java.util.Collections;
12 | import java.util.LinkedList;
13 | import java.util.List;
14 | import java.util.ResourceBundle;
15 | import java.util.prefs.Preferences;
16 |
17 | import javax.swing.JOptionPane;
18 |
19 | import net.sourceforge.pdfjumbler.ConfigKeys;
20 | import net.sourceforge.pdfjumbler.PdfJumbler;
21 | import net.sourceforge.pdfjumbler.i18n.I18nKeys;
22 | import net.sourceforge.pdfjumbler.i18n.I18nUtil;
23 | import net.sourceforge.pdfjumbler.i18n.PdfJumblerResources;
24 | import org.tinylog.Logger;
25 |
26 | /**
27 | * In der Hoffnung, dass es irgendwann einmal /eine/
28 | * Bibliothek gibt, die das alles kann...
29 | *
30 | * 2020: PdfBox <3 !
31 | *
32 | * @author Martin Gropp
33 | */
34 | public final class PdfProcessingFactory {
35 | private static final ResourceBundle resources = ResourceBundle.getBundle(PdfJumblerResources.class.getCanonicalName());
36 |
37 | private static final List> pdfEditorClasses = new ArrayList<>();
38 | private static final List> pdfRendererClasses = new ArrayList<>();
39 |
40 | // add plugins from main (could be empty)
41 | static {
42 | pdfEditorClasses.add(net.sourceforge.pdfjumbler.pdfbox.PdfEditor.class);
43 | pdfRendererClasses.add(net.sourceforge.pdfjumbler.pdfbox.PdfRenderer.class);
44 | }
45 |
46 | private static PdfEditor editor = null;
47 | private static PdfRenderer renderer = null;
48 |
49 | private static final LinkedList listeners = new LinkedList<>();
50 |
51 | static {
52 | discoverPlugins();
53 | selectInitialEditor();
54 | selectInitialRenderer();
55 | }
56 |
57 | private static void discoverPlugins() {
58 | File pluginPath = getPluginPath();
59 | if (pluginPath == null) {
60 | Logger.error("No plugin path found.");
61 | return;
62 | }
63 | Logger.info("Plugin path: {}", pluginPath);
64 |
65 | File[] jarFiles = pluginPath.listFiles(
66 | (dir, name) -> name.endsWith(".jar")
67 | );
68 | if (jarFiles == null || jarFiles.length == 0) {
69 | return;
70 | }
71 |
72 | Arrays.sort(jarFiles);
73 |
74 | for (File jarFile : jarFiles) {
75 | Logger.info("Attempting to load plugin: {}", jarFile);
76 | Plugin plugin;
77 | try {
78 | plugin = new Plugin(jarFile);
79 | }
80 | catch (IOException e) {
81 | // Ignore this plugin
82 | Logger.info("Ignoring plugin {}: {}", jarFile, e);
83 | continue;
84 | }
85 | catch (PluginException e) {
86 | Logger.error(e);
87 | JOptionPane.showMessageDialog(
88 | null,
89 | String.format(
90 | "%s: %s",
91 | String.format(
92 | resources.getString(I18nKeys.PLUGIN_INIT_ERROR),
93 | jarFile.getAbsolutePath()
94 | ),
95 | I18nUtil.getString(resources, e.getMessage(), e.getMessage())
96 | ),
97 | resources.getString(I18nKeys.PLUGIN_ERROR_TITLE),
98 | JOptionPane.ERROR_MESSAGE
99 | );
100 | System.exit(103);
101 | return;
102 | }
103 |
104 | if (plugin.hasPdfEditors()) {
105 | pdfEditorClasses.addAll(
106 | plugin.getPdfEditorClasses()
107 | );
108 | }
109 |
110 | if (plugin.hasPdfRenderers()) {
111 | pdfRendererClasses.addAll(
112 | plugin.getPdfRendererClasses()
113 | );
114 | }
115 | }
116 | }
117 |
118 | private static File getPluginPath() {
119 | String userPluginPath = System.getProperty("pdfjumbler.pluginpath", null);
120 | if (userPluginPath != null) {
121 | return new File(userPluginPath);
122 | }
123 |
124 | try {
125 | String cls = "/" + PdfProcessingFactory.class.getName().replace('.', '/') + ".class";
126 | URL url = PdfProcessingFactory.class.getResource(cls);
127 | if (url.getProtocol().equals("jar")) {
128 | String path = url.getPath();
129 | path = path.substring(0, path.indexOf('!'));
130 | URL jarURL = new URL(path);
131 | if (jarURL.getProtocol().equals("file")) {
132 | return new File(jarURL.toURI()).getParentFile();
133 | } else {
134 | return null;
135 | }
136 | } else if (url.getProtocol().equals("file")) {
137 | String path = url.getPath();
138 | if (path.endsWith(cls)) {
139 | path = path.substring(0, path.length() - cls.length());
140 | return new File(path);
141 | } else {
142 | return null;
143 | }
144 | }
145 |
146 | return null;
147 | }
148 | catch (Exception e) {
149 | return null;
150 | }
151 | }
152 |
153 | private static Class extends PdfEditor> findEditorClass(String className) {
154 | if (className == null) {
155 | return null;
156 | }
157 |
158 | for (Class extends PdfEditor> cls : pdfEditorClasses) {
159 | if (cls.getName().equals(className)) {
160 | return cls;
161 | }
162 | }
163 |
164 | return null;
165 | }
166 |
167 | private static Class extends PdfRenderer> findRendererClass(String className) {
168 | if (className == null) {
169 | return null;
170 | }
171 |
172 | for (Class extends PdfRenderer> cls : pdfRendererClasses) {
173 | if (cls.getName().equals(className)) {
174 | return cls;
175 | }
176 | }
177 |
178 | return null;
179 | }
180 |
181 | private static void selectInitialEditor() {
182 | List> classes = new ArrayList<>(pdfEditorClasses.size() + 2);
183 | classes.add(findEditorClass(
184 | System.getProperty("pdfjumbler.editor", null)
185 | ));
186 | classes.add(findEditorClass(
187 | Preferences.userNodeForPackage(PdfJumbler.class).get(ConfigKeys.EDITOR, null)
188 | ));
189 | classes.addAll(pdfEditorClasses);
190 |
191 | for (Class extends PdfEditor> cls : classes) {
192 | if (cls == null) {
193 | continue;
194 | }
195 | Logger.info("Trying to instantiate editor: " + cls + "... ");
196 | try {
197 | editor = cls.getDeclaredConstructor().newInstance();
198 | }
199 | catch (Exception e) {
200 | Logger.error(e);
201 | continue;
202 | }
203 | Logger.info("Created editor instance.");
204 | break;
205 | }
206 | if (editor == null) {
207 | Logger.error("No pdf editors found, exiting.");
208 | JOptionPane.showMessageDialog(
209 | null,
210 | resources.getString(I18nKeys.NO_PDF_EDITOR_TEXT),
211 | resources.getString(I18nKeys.NO_PDF_EDITOR_TITLE),
212 | JOptionPane.ERROR_MESSAGE
213 | );
214 | System.exit(101);
215 | }
216 | }
217 |
218 | private static void selectInitialRenderer() {
219 | List> classes = new ArrayList<>(pdfRendererClasses.size() + 2);
220 | classes.add(findRendererClass(
221 | System.getProperty("pdfjumbler.renderer", null)
222 | ));
223 | classes.add(findRendererClass(
224 | Preferences.userNodeForPackage(PdfJumbler.class).get(ConfigKeys.RENDERER, null)
225 | ));
226 | classes.addAll(pdfRendererClasses);
227 |
228 | for (Class extends PdfRenderer> cls : classes) {
229 | if (cls == null) {
230 | continue;
231 | }
232 |
233 | Logger.info("Trying to instantiate renderer: " + cls + "... ");
234 | try {
235 | renderer = cls.getDeclaredConstructor().newInstance();
236 | }
237 | catch (Exception e) {
238 | Logger.error(e);
239 | continue;
240 | }
241 | Logger.info("Created renderer instance.");
242 | break;
243 | }
244 | if (renderer == null) {
245 | Logger.error("No pdf renderers found, exiting.");
246 | JOptionPane.showMessageDialog(
247 | null,
248 | resources.getString(I18nKeys.NO_PDF_RENDERER_TEXT),
249 | resources.getString(I18nKeys.NO_PDF_RENDERER_TITLE),
250 | JOptionPane.ERROR_MESSAGE
251 | );
252 | System.exit(102);
253 | }
254 | }
255 |
256 | public static PdfEditor getEditor() {
257 | return editor;
258 | }
259 |
260 | public static PdfRenderer getRenderer() {
261 | return renderer;
262 | }
263 |
264 | public static Class extends PdfEditor> getEditorClass() {
265 | return editor.getClass();
266 | }
267 |
268 | public static Class extends PdfRenderer> getRendererClass() {
269 | return renderer.getClass();
270 | }
271 |
272 | public static void setEditorClass(Class extends PdfEditor> cls) throws InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
273 | PdfEditor oldEditor = editor;
274 | editor = cls.getDeclaredConstructor().newInstance();
275 | fireEditorChanged(oldEditor);
276 | Preferences.userNodeForPackage(PdfJumbler.class).put(ConfigKeys.EDITOR, cls.getCanonicalName());
277 | }
278 |
279 | public static void setRendererClass(Class extends PdfRenderer> cls) throws InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
280 | PdfRenderer oldRenderer = renderer;
281 | renderer = cls.getDeclaredConstructor().newInstance();
282 | fireRendererChanged(oldRenderer);
283 | Preferences.userNodeForPackage(PdfJumbler.class).put(ConfigKeys.RENDERER, cls.getCanonicalName());
284 | }
285 |
286 | public static String getFriendlyName(Class> cls) {
287 | try {
288 | Method m = cls.getMethod("getFriendlyName");
289 | return (String)m.invoke(null);
290 | }
291 | catch (Exception e) {
292 | return cls.getCanonicalName();
293 | }
294 | }
295 |
296 | public static Collection> getAvailableEditors() {
297 | return Collections.unmodifiableCollection(pdfEditorClasses);
298 | }
299 |
300 | public static Collection> getAvailableRenderers() {
301 | return Collections.unmodifiableCollection(pdfRendererClasses);
302 | }
303 |
304 | private static void fireEditorChanged(PdfEditor oldEditor) {
305 | for (PdfProcessorListener listener : listeners) {
306 | Logger.trace("Editor changed, notifying pdf processor listener: {}", listener);
307 | listener.pdfEditorChanged(oldEditor, editor);
308 | }
309 | }
310 |
311 | private static void fireRendererChanged(PdfRenderer oldRenderer) {
312 | for (PdfProcessorListener listener : listeners) {
313 | Logger.trace("Renderer changed, notifying pdf processor listener: {}", listener);
314 | listener.pdfRendererChanged(oldRenderer, renderer);
315 | }
316 | }
317 |
318 | public static void addProcessorListener(PdfProcessorListener listener) {
319 | listeners.add(listener);
320 | }
321 |
322 | public static void removeProcessorListener(PdfProcessorListener listener) {
323 | listeners.remove(listener);
324 | }
325 | }
--------------------------------------------------------------------------------
/src/main/java/net/sourceforge/pdfjumbler/jdragdroplist/UndoableList.java:
--------------------------------------------------------------------------------
1 | package net.sourceforge.pdfjumbler.jdragdroplist;
2 |
3 | import java.util.ArrayList;
4 | import java.util.Collection;
5 | import java.util.ConcurrentModificationException;
6 | import java.util.Deque;
7 | import java.util.Iterator;
8 | import java.util.LinkedList;
9 | import java.util.List;
10 | import java.util.ListIterator;
11 |
12 | import javax.swing.event.ListDataEvent;
13 | import javax.swing.event.ListDataListener;
14 | import javax.swing.event.UndoableEditEvent;
15 | import javax.swing.event.UndoableEditListener;
16 | import javax.swing.undo.CompoundEdit;
17 | import javax.swing.undo.UndoableEdit;
18 |
19 | public class UndoableList implements List {
20 | private final List list;
21 | private final UndoableList parent;
22 | private final int offset;
23 | private final List editListeners;
24 | private final List listDataListeners;
25 |
26 | /*
27 | * This is not meant to be thread-safe, but to prevent
28 | * listeners from modifying the list.
29 | */
30 | private final boolean[] editProtection;
31 |
32 | private final Deque compoundEdits = new LinkedList<>();
33 |
34 | public UndoableList(List list) {
35 | this.list = list;
36 | this.parent = null;
37 | this.offset = 0;
38 | this.editProtection = new boolean[]{ false };
39 | this.editListeners = new ArrayList<>();
40 | this.listDataListeners = new ArrayList<>();
41 | }
42 |
43 | private UndoableList(UndoableList parent, List subList, int offset) {
44 | this.list = subList;
45 | this.parent = parent;
46 | this.offset = offset;
47 | this.editProtection = parent.editProtection;
48 | this.editListeners = parent.editListeners;
49 | this.listDataListeners = parent.listDataListeners;
50 | }
51 |
52 | private UndoableList getMainList() {
53 | return (parent == null) ? this : parent;
54 | }
55 |
56 | public void addUndoableEditListener(UndoableEditListener l) {
57 | editListeners.add(l);
58 | }
59 |
60 | public void removeUndoableEditListener(UndoableEditListener l) {
61 | editListeners.remove(l);
62 | }
63 |
64 | public void addListDataListener(ListDataListener l) {
65 | listDataListeners.add(l);
66 | }
67 |
68 | public void removeListDataListener(ListDataListener l) {
69 | listDataListeners.remove(l);
70 | }
71 |
72 | public void beginCompoundEdit(final String name) {
73 | CompoundEdit edit = new CompoundEdit() {
74 | private static final long serialVersionUID = 2894975385303438798L;
75 |
76 | @Override
77 | public String getPresentationName() {
78 | return name;
79 | }
80 | };
81 | compoundEdits.push(edit);
82 | }
83 |
84 | public void beginCompoundEdit(CompoundEdit edit) {
85 | compoundEdits.push(edit);
86 | }
87 |
88 | public void endCompoundEdit() {
89 | CompoundEdit edit = compoundEdits.pop();
90 | edit.end();
91 | addEdit(edit);
92 | }
93 |
94 | private void addEdit(UndoableEdit edit) {
95 | if (compoundEdits.isEmpty()) {
96 | UndoableEditEvent editEvent = new UndoableEditEvent(getMainList(), edit);
97 | for (UndoableEditListener l : editListeners) {
98 | l.undoableEditHappened(editEvent);
99 | }
100 | } else {
101 | compoundEdits.peek().addEdit(edit);
102 | }
103 | }
104 |
105 | private void callListenersAdd(ListEdit edit, ListDataEvent dataEvent) {
106 | assert (!editProtection[0]);
107 |
108 | editProtection[0] = true;
109 | try {
110 | if (edit != null) {
111 | addEdit(edit);
112 | }
113 |
114 | if (dataEvent != null) {
115 | for (ListDataListener l : listDataListeners) {
116 | l.intervalAdded(dataEvent);
117 | }
118 | }
119 | }
120 | finally {
121 | editProtection[0] = false;
122 | }
123 | }
124 |
125 | private void callListenersRemove(ListEdit edit, ListDataEvent dataEvent) {
126 | assert (!editProtection[0]);
127 |
128 | editProtection[0] = true;
129 | try {
130 | if (edit != null) {
131 | addEdit(edit);
132 | }
133 |
134 | for (ListDataListener l : listDataListeners) {
135 | l.intervalRemoved(dataEvent);
136 | }
137 | }
138 | finally {
139 | editProtection[0] = false;
140 | }
141 | }
142 |
143 | private void callListenersSet(ListEdit edit, ListDataEvent dataEvent) {
144 | assert (!editProtection[0]);
145 |
146 | editProtection[0] = true;
147 | try {
148 | if (edit != null) {
149 | addEdit(edit);
150 | }
151 |
152 | if (dataEvent != null) {
153 | for (ListDataListener l : listDataListeners) {
154 | l.contentsChanged(dataEvent);
155 | }
156 | }
157 | }
158 | finally {
159 | editProtection[0] = false;
160 | }
161 | }
162 |
163 | @Override
164 | public boolean add(T element) {
165 | add(size(), element);
166 | return true;
167 | }
168 |
169 | @Override
170 | public void add(int index, T element) {
171 | if (editProtection[0]) {
172 | throw new ConcurrentModificationException();
173 | }
174 |
175 | list.add(index, element);
176 | callListenersAdd(
177 | new ListEdit<>(ListEdit.Type.ADD, getMainList(), index + offset, element),
178 | new ListDataEvent(getMainList(), ListDataEvent.INTERVAL_ADDED, index + offset, index)
179 | );
180 | }
181 |
182 | @Override
183 | public boolean addAll(Collection extends T> c) {
184 | return addAll(size(), c);
185 | }
186 |
187 | @Override
188 | public boolean addAll(int index, Collection extends T> c) {
189 | if (editProtection[0]) {
190 | throw new ConcurrentModificationException();
191 | }
192 |
193 | list.addAll(index, c);
194 | callListenersAdd(
195 | new ListEdit<>(ListEdit.Type.ADD_MULTIPLE, getMainList(), index + offset, c),
196 | new ListDataEvent(getMainList(), ListDataEvent.INTERVAL_ADDED, index + offset, index + offset + c.size() - 1)
197 | );
198 | return true;
199 | }
200 |
201 | @Override
202 | public void clear() {
203 | if (editProtection[0]) {
204 | throw new ConcurrentModificationException();
205 | }
206 |
207 | Collection copy = new ArrayList<>(list);
208 | list.clear();
209 | callListenersRemove(
210 | new ListEdit<>(ListEdit.Type.REMOVE_MULTIPLE, getMainList(), offset, copy),
211 | new ListDataEvent(getMainList(), ListDataEvent.INTERVAL_REMOVED, offset, offset + copy.size() - 1)
212 | );
213 | }
214 |
215 | @Override
216 | public boolean contains(Object o) {
217 | return list.contains(o);
218 | }
219 |
220 | @Override
221 | public boolean containsAll(Collection> c) {
222 | return list.containsAll(c);
223 | }
224 |
225 | @Override
226 | public T get(int index) {
227 | return list.get(index);
228 | }
229 |
230 | @Override
231 | public int indexOf(Object o) {
232 | return list.indexOf(o);
233 | }
234 |
235 | @Override
236 | public boolean isEmpty() {
237 | return list.isEmpty();
238 | }
239 |
240 | @Override
241 | public Iterator iterator() {
242 | return listIterator();
243 | }
244 |
245 | @Override
246 | public int lastIndexOf(Object o) {
247 | return list.lastIndexOf(o);
248 | }
249 |
250 | @Override
251 | public ListIterator listIterator(int index) {
252 | final ListIterator it = list.listIterator(index);
253 |
254 | return new ListIterator<>() {
255 | @Override
256 | public boolean hasNext() {
257 | return it.hasNext();
258 | }
259 |
260 | @Override
261 | public boolean hasPrevious() {
262 | return it.hasPrevious();
263 | }
264 |
265 | @Override
266 | public T next() {
267 | return it.next();
268 | }
269 |
270 | @Override
271 | public int nextIndex() {
272 | return it.nextIndex();
273 | }
274 |
275 | @Override
276 | public T previous() {
277 | return it.previous();
278 | }
279 |
280 | @Override
281 | public int previousIndex() {
282 | return it.previousIndex();
283 | }
284 |
285 | @Override
286 | public void add(T e) {
287 | throw new UnsupportedOperationException();
288 | }
289 |
290 | @Override
291 | public void remove() {
292 | throw new UnsupportedOperationException();
293 | }
294 |
295 | @Override
296 | public void set(T e) {
297 | throw new UnsupportedOperationException();
298 | }
299 | };
300 | }
301 |
302 | @Override
303 | public ListIterator listIterator() {
304 | return listIterator(0);
305 | }
306 |
307 | @Override
308 | public boolean remove(Object o) {
309 | int index = indexOf(o);
310 | if (index >= 0) {
311 | remove(index);
312 | return true;
313 | } else {
314 | return false;
315 | }
316 | }
317 |
318 | @Override
319 | public T remove(int index) {
320 | if (editProtection[0]) {
321 | throw new ConcurrentModificationException();
322 | }
323 |
324 | T item = list.remove(index);
325 | callListenersRemove(
326 | new ListEdit<>(ListEdit.Type.REMOVE, getMainList(), index + offset, item),
327 | new ListDataEvent(getMainList(), ListDataEvent.INTERVAL_REMOVED, index + offset, index + offset)
328 | );
329 |
330 | return item;
331 | }
332 |
333 | @Override
334 | public boolean removeAll(Collection> c) {
335 | boolean changed = false;
336 | beginCompoundEdit("Remove");
337 | try {
338 | for (Object o : c) {
339 | changed |= remove(o);
340 | }
341 | }
342 | finally {
343 | endCompoundEdit();
344 | }
345 |
346 | return changed;
347 | }
348 |
349 | @Override
350 | public boolean retainAll(Collection> c) {
351 | ArrayList removeItems = new ArrayList<>();
352 | for (T item : list) {
353 | if (!c.contains(item)) {
354 | removeItems.add(item);
355 | }
356 | }
357 |
358 | return removeAll(removeItems);
359 | }
360 |
361 | @Override
362 | public T set(int index, T element) {
363 | if (editProtection[0]) {
364 | throw new ConcurrentModificationException();
365 | }
366 |
367 | T oldElement = list.set(index, element);
368 | callListenersSet(
369 | new ListEdit<>(ListEdit.Type.SET, getMainList(), index + offset, oldElement, element),
370 | new ListDataEvent(getMainList(), ListDataEvent.CONTENTS_CHANGED, index + offset, index + offset)
371 | );
372 |
373 | return oldElement;
374 | }
375 |
376 | @Override
377 | public int size() {
378 | return list.size();
379 | }
380 |
381 | @Override
382 | public List subList(int fromIndex, int toIndex) {
383 | return new UndoableList<>(getMainList(), list.subList(fromIndex, toIndex), fromIndex + offset);
384 | }
385 |
386 | @Override
387 | public Object[] toArray() {
388 | return list.toArray();
389 | }
390 |
391 | @Override
392 | public U[] toArray(U[] a) {
393 | return list.toArray(a);
394 | }
395 |
396 | private void undoAdd(ListEdit edit) {
397 | assert (edit.list.list.get(edit.index) == edit.item);
398 | edit.list.list.remove(edit.index);
399 | callListenersRemove(
400 | null,
401 | new ListDataEvent(edit.list, ListDataEvent.INTERVAL_REMOVED, edit.index, edit.index)
402 | );
403 | }
404 |
405 | private void undoRemove(ListEdit edit) {
406 | edit.list.list.add(edit.index, edit.item);
407 | callListenersAdd(
408 | null,
409 | new ListDataEvent(edit.list, ListDataEvent.INTERVAL_ADDED, edit.index, edit.index)
410 | );
411 | }
412 |
413 | private void undoAddMultiple(ListEdit edit) {
414 | if (edit.items == null) {
415 | throw new IllegalArgumentException("edit.items is null!");
416 | }
417 | for (T item : edit.items) {
418 | assert (edit.list.list.get(edit.index) == item);
419 | edit.list.list.remove(edit.index);
420 | }
421 | callListenersRemove(
422 | null,
423 | new ListDataEvent(edit.list, ListDataEvent.INTERVAL_REMOVED, edit.index, edit.index + edit.items.size() - 1)
424 | );
425 | }
426 |
427 | private void undoRemoveMultiple(ListEdit edit) {
428 | if (edit.items == null) {
429 | throw new IllegalArgumentException("edit.items is null!");
430 | }
431 | int index = edit.index;
432 | for (T item : edit.items) {
433 | edit.list.list.add(index++, item);
434 | }
435 | callListenersAdd(
436 | null,
437 | new ListDataEvent(edit.list, ListDataEvent.INTERVAL_ADDED, edit.index, edit.index + edit.items.size())
438 | );
439 | }
440 |
441 | private void undoSet(ListEdit edit) {
442 | edit.list.list.set(edit.index, edit.item);
443 | callListenersSet(
444 | null,
445 | new ListDataEvent(edit.list, ListDataEvent.CONTENTS_CHANGED, edit.index, edit.index)
446 | );
447 | }
448 |
449 | private void redoSet(ListEdit edit) {
450 | edit.list.list.set(edit.index, edit.item2);
451 | callListenersSet(
452 | null,
453 | new ListDataEvent(edit.list, ListDataEvent.CONTENTS_CHANGED, edit.index, edit.index)
454 | );
455 | }
456 |
457 | void undo(ListEdit edit) {
458 | if (editProtection[0]) {
459 | throw new ConcurrentModificationException();
460 | }
461 | if (edit.list != getMainList()) {
462 | throw new IllegalArgumentException();
463 | }
464 |
465 | switch (edit.type) {
466 | case ADD:
467 | undoAdd(edit);
468 | break;
469 | case REMOVE:
470 | undoRemove(edit);
471 | break;
472 | case ADD_MULTIPLE:
473 | undoAddMultiple(edit);
474 | break;
475 | case REMOVE_MULTIPLE:
476 | undoRemoveMultiple(edit);
477 | break;
478 | case SET:
479 | undoSet(edit);
480 | break;
481 | default:
482 | throw new AssertionError();
483 | }
484 | }
485 |
486 | void redo(ListEdit edit) {
487 | if (editProtection[0]) {
488 | throw new ConcurrentModificationException();
489 | }
490 |
491 | switch (edit.type) {
492 | case ADD:
493 | undoRemove(edit);
494 | break;
495 | case REMOVE:
496 | undoAdd(edit);
497 | break;
498 | case ADD_MULTIPLE:
499 | undoRemoveMultiple(edit);
500 | break;
501 | case REMOVE_MULTIPLE:
502 | undoAddMultiple(edit);
503 | break;
504 | case SET:
505 | redoSet(edit);
506 | break;
507 | default:
508 | throw new AssertionError();
509 | }
510 | }
511 |
512 | @Override
513 | public boolean equals(Object other) {
514 | if (other instanceof UndoableList) {
515 | if (((UndoableList>)other).size() != size()) {
516 | return false;
517 | }
518 |
519 | Iterator> it1 = iterator();
520 | Iterator> it2 = ((UndoableList>)other).iterator();
521 |
522 | while (it1.hasNext()) {
523 | Object o1 = it1.next();
524 | Object o2 = it2.next();
525 | if (o1 == null) {
526 | if (o2 != null) {
527 | return false;
528 | }
529 | } else if (!o1.equals(o2)) {
530 | return false;
531 | }
532 | }
533 |
534 | return true;
535 | } else {
536 | return false;
537 | }
538 | }
539 |
540 | @Override
541 | public int hashCode() {
542 | int hashCode = 1;
543 | for (Object obj : this) {
544 | hashCode = 31*hashCode + (obj == null ? 0 : obj.hashCode());
545 | }
546 | return hashCode;
547 | }
548 |
549 | @Override
550 | public String toString() {
551 | StringBuilder sb = new StringBuilder();
552 | sb.append('[');
553 | for (T item : this) {
554 | sb.append(' ');
555 | sb.append(item.toString());
556 | }
557 | sb.append(" ]");
558 |
559 | return sb.toString();
560 | }
561 |
562 | public static void main(String[] args) {
563 | final LinkedList edits = new LinkedList<>();
564 | UndoableEditListener editListener = new UndoableEditListener() {
565 | @Override
566 | public void undoableEditHappened(UndoableEditEvent e) {
567 | System.out.println(e.getEdit());
568 | edits.addFirst(e.getEdit());
569 | }
570 | };
571 |
572 | UndoableList list = new UndoableList<>(new LinkedList<>());
573 | list.addUndoableEditListener(editListener);
574 | for (int i = 1; i < 10; i++) {
575 | list.add(2*i);
576 | }
577 | System.out.println(list);
578 | list.add(6, 13);
579 | System.out.println(list);
580 | list.set(3, 7);
581 | System.out.println(list);
582 |
583 | List subList = list.subList(3, 6);
584 | System.out.println(subList);
585 | subList.set(0, 8);
586 | System.out.println(list);
587 | System.out.println(subList);
588 | subList.remove(1);
589 |
590 | System.out.println("----------------------------------");
591 | for (UndoableEdit edit : edits) {
592 | System.out.println("Undo " + edit);
593 | edit.undo();
594 | System.out.println(list);
595 | }
596 | }
597 | }
598 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 | Preamble
9 |
10 | The GNU General Public License is a free, copyleft license for
11 | software and other kinds of works.
12 |
13 | The licenses for most software and other practical works are designed
14 | to take away your freedom to share and change the works. By contrast,
15 | the GNU General Public License is intended to guarantee your freedom to
16 | share and change all versions of a program--to make sure it remains free
17 | software for all its users. We, the Free Software Foundation, use the
18 | GNU General Public License for most of our software; it applies also to
19 | any other work released this way by its authors. You can apply it to
20 | your programs, too.
21 |
22 | When we speak of free software, we are referring to freedom, not
23 | price. Our General Public Licenses are designed to make sure that you
24 | have the freedom to distribute copies of free software (and charge for
25 | them if you wish), that you receive source code or can get it if you
26 | want it, that you can change the software or use pieces of it in new
27 | free programs, and that you know you can do these things.
28 |
29 | To protect your rights, we need to prevent others from denying you
30 | these rights or asking you to surrender the rights. Therefore, you have
31 | certain responsibilities if you distribute copies of the software, or if
32 | you modify it: responsibilities to respect the freedom of others.
33 |
34 | For example, if you distribute copies of such a program, whether
35 | gratis or for a fee, you must pass on to the recipients the same
36 | freedoms that you received. You must make sure that they, too, receive
37 | or can get the source code. And you must show them these terms so they
38 | know their rights.
39 |
40 | Developers that use the GNU GPL protect your rights with two steps:
41 | (1) assert copyright on the software, and (2) offer you this License
42 | giving you legal permission to copy, distribute and/or modify it.
43 |
44 | For the developers' and authors' protection, the GPL clearly explains
45 | that there is no warranty for this free software. For both users' and
46 | authors' sake, the GPL requires that modified versions be marked as
47 | changed, so that their problems will not be attributed erroneously to
48 | authors of previous versions.
49 |
50 | Some devices are designed to deny users access to install or run
51 | modified versions of the software inside them, although the manufacturer
52 | can do so. This is fundamentally incompatible with the aim of
53 | protecting users' freedom to change the software. The systematic
54 | pattern of such abuse occurs in the area of products for individuals to
55 | use, which is precisely where it is most unacceptable. Therefore, we
56 | have designed this version of the GPL to prohibit the practice for those
57 | products. If such problems arise substantially in other domains, we
58 | stand ready to extend this provision to those domains in future versions
59 | of the GPL, as needed to protect the freedom of users.
60 |
61 | Finally, every program is threatened constantly by software patents.
62 | States should not allow patents to restrict development and use of
63 | software on general-purpose computers, but in those that do, we wish to
64 | avoid the special danger that patents applied to a free program could
65 | make it effectively proprietary. To prevent this, the GPL assures that
66 | patents cannot be used to render the program non-free.
67 |
68 | The precise terms and conditions for copying, distribution and
69 | modification follow.
70 |
71 | TERMS AND CONDITIONS
72 |
73 | 0. Definitions.
74 |
75 | "This License" refers to version 3 of the GNU General Public License.
76 |
77 | "Copyright" also means copyright-like laws that apply to other kinds of
78 | works, such as semiconductor masks.
79 |
80 | "The Program" refers to any copyrightable work licensed under this
81 | License. Each licensee is addressed as "you". "Licensees" and
82 | "recipients" may be individuals or organizations.
83 |
84 | To "modify" a work means to copy from or adapt all or part of the work
85 | in a fashion requiring copyright permission, other than the making of an
86 | exact copy. The resulting work is called a "modified version" of the
87 | earlier work or a work "based on" the earlier work.
88 |
89 | A "covered work" means either the unmodified Program or a work based
90 | on the Program.
91 |
92 | To "propagate" a work means to do anything with it that, without
93 | permission, would make you directly or secondarily liable for
94 | infringement under applicable copyright law, except executing it on a
95 | computer or modifying a private copy. Propagation includes copying,
96 | distribution (with or without modification), making available to the
97 | public, and in some countries other activities as well.
98 |
99 | To "convey" a work means any kind of propagation that enables other
100 | parties to make or receive copies. Mere interaction with a user through
101 | a computer network, with no transfer of a copy, is not conveying.
102 |
103 | An interactive user interface displays "Appropriate Legal Notices"
104 | to the extent that it includes a convenient and prominently visible
105 | feature that (1) displays an appropriate copyright notice, and (2)
106 | tells the user that there is no warranty for the work (except to the
107 | extent that warranties are provided), that licensees may convey the
108 | work under this License, and how to view a copy of this License. If
109 | the interface presents a list of user commands or options, such as a
110 | menu, a prominent item in the list meets this criterion.
111 |
112 | 1. Source Code.
113 |
114 | The "source code" for a work means the preferred form of the work
115 | for making modifications to it. "Object code" means any non-source
116 | form of a work.
117 |
118 | A "Standard Interface" means an interface that either is an official
119 | standard defined by a recognized standards body, or, in the case of
120 | interfaces specified for a particular programming language, one that
121 | is widely used among developers working in that language.
122 |
123 | The "System Libraries" of an executable work include anything, other
124 | than the work as a whole, that (a) is included in the normal form of
125 | packaging a Major Component, but which is not part of that Major
126 | Component, and (b) serves only to enable use of the work with that
127 | Major Component, or to implement a Standard Interface for which an
128 | implementation is available to the public in source code form. A
129 | "Major Component", in this context, means a major essential component
130 | (kernel, window system, and so on) of the specific operating system
131 | (if any) on which the executable work runs, or a compiler used to
132 | produce the work, or an object code interpreter used to run it.
133 |
134 | The "Corresponding Source" for a work in object code form means all
135 | the source code needed to generate, install, and (for an executable
136 | work) run the object code and to modify the work, including scripts to
137 | control those activities. However, it does not include the work's
138 | System Libraries, or general-purpose tools or generally available free
139 | programs which are used unmodified in performing those activities but
140 | which are not part of the work. For example, Corresponding Source
141 | includes interface definition files associated with source files for
142 | the work, and the source code for shared libraries and dynamically
143 | linked subprograms that the work is specifically designed to require,
144 | such as by intimate data communication or control flow between those
145 | subprograms and other parts of the work.
146 |
147 | The Corresponding Source need not include anything that users
148 | can regenerate automatically from other parts of the Corresponding
149 | Source.
150 |
151 | The Corresponding Source for a work in source code form is that
152 | same work.
153 |
154 | 2. Basic Permissions.
155 |
156 | All rights granted under this License are granted for the term of
157 | copyright on the Program, and are irrevocable provided the stated
158 | conditions are met. This License explicitly affirms your unlimited
159 | permission to run the unmodified Program. The output from running a
160 | covered work is covered by this License only if the output, given its
161 | content, constitutes a covered work. This License acknowledges your
162 | rights of fair use or other equivalent, as provided by copyright law.
163 |
164 | You may make, run and propagate covered works that you do not
165 | convey, without conditions so long as your license otherwise remains
166 | in force. You may convey covered works to others for the sole purpose
167 | of having them make modifications exclusively for you, or provide you
168 | with facilities for running those works, provided that you comply with
169 | the terms of this License in conveying all material for which you do
170 | not control copyright. Those thus making or running the covered works
171 | for you must do so exclusively on your behalf, under your direction
172 | and control, on terms that prohibit them from making any copies of
173 | your copyrighted material outside their relationship with you.
174 |
175 | Conveying under any other circumstances is permitted solely under
176 | the conditions stated below. Sublicensing is not allowed; section 10
177 | makes it unnecessary.
178 |
179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
180 |
181 | No covered work shall be deemed part of an effective technological
182 | measure under any applicable law fulfilling obligations under article
183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or
184 | similar laws prohibiting or restricting circumvention of such
185 | measures.
186 |
187 | When you convey a covered work, you waive any legal power to forbid
188 | circumvention of technological measures to the extent such circumvention
189 | is effected by exercising rights under this License with respect to
190 | the covered work, and you disclaim any intention to limit operation or
191 | modification of the work as a means of enforcing, against the work's
192 | users, your or third parties' legal rights to forbid circumvention of
193 | technological measures.
194 |
195 | 4. Conveying Verbatim Copies.
196 |
197 | You may convey verbatim copies of the Program's source code as you
198 | receive it, in any medium, provided that you conspicuously and
199 | appropriately publish on each copy an appropriate copyright notice;
200 | keep intact all notices stating that this License and any
201 | non-permissive terms added in accord with section 7 apply to the code;
202 | keep intact all notices of the absence of any warranty; and give all
203 | recipients a copy of this License along with the Program.
204 |
205 | You may charge any price or no price for each copy that you convey,
206 | and you may offer support or warranty protection for a fee.
207 |
208 | 5. Conveying Modified Source Versions.
209 |
210 | You may convey a work based on the Program, or the modifications to
211 | produce it from the Program, in the form of source code under the
212 | terms of section 4, provided that you also meet all of these conditions:
213 |
214 | a) The work must carry prominent notices stating that you modified
215 | it, and giving a relevant date.
216 |
217 | b) The work must carry prominent notices stating that it is
218 | released under this License and any conditions added under section
219 | 7. This requirement modifies the requirement in section 4 to
220 | "keep intact all notices".
221 |
222 | c) You must license the entire work, as a whole, under this
223 | License to anyone who comes into possession of a copy. This
224 | License will therefore apply, along with any applicable section 7
225 | additional terms, to the whole of the work, and all its parts,
226 | regardless of how they are packaged. This License gives no
227 | permission to license the work in any other way, but it does not
228 | invalidate such permission if you have separately received it.
229 |
230 | d) If the work has interactive user interfaces, each must display
231 | Appropriate Legal Notices; however, if the Program has interactive
232 | interfaces that do not display Appropriate Legal Notices, your
233 | work need not make them do so.
234 |
235 | A compilation of a covered work with other separate and independent
236 | works, which are not by their nature extensions of the covered work,
237 | and which are not combined with it such as to form a larger program,
238 | in or on a volume of a storage or distribution medium, is called an
239 | "aggregate" if the compilation and its resulting copyright are not
240 | used to limit the access or legal rights of the compilation's users
241 | beyond what the individual works permit. Inclusion of a covered work
242 | in an aggregate does not cause this License to apply to the other
243 | parts of the aggregate.
244 |
245 | 6. Conveying Non-Source Forms.
246 |
247 | You may convey a covered work in object code form under the terms
248 | of sections 4 and 5, provided that you also convey the
249 | machine-readable Corresponding Source under the terms of this License,
250 | in one of these ways:
251 |
252 | a) Convey the object code in, or embodied in, a physical product
253 | (including a physical distribution medium), accompanied by the
254 | Corresponding Source fixed on a durable physical medium
255 | customarily used for software interchange.
256 |
257 | b) Convey the object code in, or embodied in, a physical product
258 | (including a physical distribution medium), accompanied by a
259 | written offer, valid for at least three years and valid for as
260 | long as you offer spare parts or customer support for that product
261 | model, to give anyone who possesses the object code either (1) a
262 | copy of the Corresponding Source for all the software in the
263 | product that is covered by this License, on a durable physical
264 | medium customarily used for software interchange, for a price no
265 | more than your reasonable cost of physically performing this
266 | conveying of source, or (2) access to copy the
267 | Corresponding Source from a network server at no charge.
268 |
269 | c) Convey individual copies of the object code with a copy of the
270 | written offer to provide the Corresponding Source. This
271 | alternative is allowed only occasionally and noncommercially, and
272 | only if you received the object code with such an offer, in accord
273 | with subsection 6b.
274 |
275 | d) Convey the object code by offering access from a designated
276 | place (gratis or for a charge), and offer equivalent access to the
277 | Corresponding Source in the same way through the same place at no
278 | further charge. You need not require recipients to copy the
279 | Corresponding Source along with the object code. If the place to
280 | copy the object code is a network server, the Corresponding Source
281 | may be on a different server (operated by you or a third party)
282 | that supports equivalent copying facilities, provided you maintain
283 | clear directions next to the object code saying where to find the
284 | Corresponding Source. Regardless of what server hosts the
285 | Corresponding Source, you remain obligated to ensure that it is
286 | available for as long as needed to satisfy these requirements.
287 |
288 | e) Convey the object code using peer-to-peer transmission, provided
289 | you inform other peers where the object code and Corresponding
290 | Source of the work are being offered to the general public at no
291 | charge under subsection 6d.
292 |
293 | A separable portion of the object code, whose source code is excluded
294 | from the Corresponding Source as a System Library, need not be
295 | included in conveying the object code work.
296 |
297 | A "User Product" is either (1) a "consumer product", which means any
298 | tangible personal property which is normally used for personal, family,
299 | or household purposes, or (2) anything designed or sold for incorporation
300 | into a dwelling. In determining whether a product is a consumer product,
301 | doubtful cases shall be resolved in favor of coverage. For a particular
302 | product received by a particular user, "normally used" refers to a
303 | typical or common use of that class of product, regardless of the status
304 | of the particular user or of the way in which the particular user
305 | actually uses, or expects or is expected to use, the product. A product
306 | is a consumer product regardless of whether the product has substantial
307 | commercial, industrial or non-consumer uses, unless such uses represent
308 | the only significant mode of use of the product.
309 |
310 | "Installation Information" for a User Product means any methods,
311 | procedures, authorization keys, or other information required to install
312 | and execute modified versions of a covered work in that User Product from
313 | a modified version of its Corresponding Source. The information must
314 | suffice to ensure that the continued functioning of the modified object
315 | code is in no case prevented or interfered with solely because
316 | modification has been made.
317 |
318 | If you convey an object code work under this section in, or with, or
319 | specifically for use in, a User Product, and the conveying occurs as
320 | part of a transaction in which the right of possession and use of the
321 | User Product is transferred to the recipient in perpetuity or for a
322 | fixed term (regardless of how the transaction is characterized), the
323 | Corresponding Source conveyed under this section must be accompanied
324 | by the Installation Information. But this requirement does not apply
325 | if neither you nor any third party retains the ability to install
326 | modified object code on the User Product (for example, the work has
327 | been installed in ROM).
328 |
329 | The requirement to provide Installation Information does not include a
330 | requirement to continue to provide support service, warranty, or updates
331 | for a work that has been modified or installed by the recipient, or for
332 | the User Product in which it has been modified or installed. Access to a
333 | network may be denied when the modification itself materially and
334 | adversely affects the operation of the network or violates the rules and
335 | protocols for communication across the network.
336 |
337 | Corresponding Source conveyed, and Installation Information provided,
338 | in accord with this section must be in a format that is publicly
339 | documented (and with an implementation available to the public in
340 | source code form), and must require no special password or key for
341 | unpacking, reading or copying.
342 |
343 | 7. Additional Terms.
344 |
345 | "Additional permissions" are terms that supplement the terms of this
346 | License by making exceptions from one or more of its conditions.
347 | Additional permissions that are applicable to the entire Program shall
348 | be treated as though they were included in this License, to the extent
349 | that they are valid under applicable law. If additional permissions
350 | apply only to part of the Program, that part may be used separately
351 | under those permissions, but the entire Program remains governed by
352 | this License without regard to the additional permissions.
353 |
354 | When you convey a copy of a covered work, you may at your option
355 | remove any additional permissions from that copy, or from any part of
356 | it. (Additional permissions may be written to require their own
357 | removal in certain cases when you modify the work.) You may place
358 | additional permissions on material, added by you to a covered work,
359 | for which you have or can give appropriate copyright permission.
360 |
361 | Notwithstanding any other provision of this License, for material you
362 | add to a covered work, you may (if authorized by the copyright holders of
363 | that material) supplement the terms of this License with terms:
364 |
365 | a) Disclaiming warranty or limiting liability differently from the
366 | terms of sections 15 and 16 of this License; or
367 |
368 | b) Requiring preservation of specified reasonable legal notices or
369 | author attributions in that material or in the Appropriate Legal
370 | Notices displayed by works containing it; or
371 |
372 | c) Prohibiting misrepresentation of the origin of that material, or
373 | requiring that modified versions of such material be marked in
374 | reasonable ways as different from the original version; or
375 |
376 | d) Limiting the use for publicity purposes of names of licensors or
377 | authors of the material; or
378 |
379 | e) Declining to grant rights under trademark law for use of some
380 | trade names, trademarks, or service marks; or
381 |
382 | f) Requiring indemnification of licensors and authors of that
383 | material by anyone who conveys the material (or modified versions of
384 | it) with contractual assumptions of liability to the recipient, for
385 | any liability that these contractual assumptions directly impose on
386 | those licensors and authors.
387 |
388 | All other non-permissive additional terms are considered "further
389 | restrictions" within the meaning of section 10. If the Program as you
390 | received it, or any part of it, contains a notice stating that it is
391 | governed by this License along with a term that is a further
392 | restriction, you may remove that term. If a license document contains
393 | a further restriction but permits relicensing or conveying under this
394 | License, you may add to a covered work material governed by the terms
395 | of that license document, provided that the further restriction does
396 | not survive such relicensing or conveying.
397 |
398 | If you add terms to a covered work in accord with this section, you
399 | must place, in the relevant source files, a statement of the
400 | additional terms that apply to those files, or a notice indicating
401 | where to find the applicable terms.
402 |
403 | Additional terms, permissive or non-permissive, may be stated in the
404 | form of a separately written license, or stated as exceptions;
405 | the above requirements apply either way.
406 |
407 | 8. Termination.
408 |
409 | You may not propagate or modify a covered work except as expressly
410 | provided under this License. Any attempt otherwise to propagate or
411 | modify it is void, and will automatically terminate your rights under
412 | this License (including any patent licenses granted under the third
413 | paragraph of section 11).
414 |
415 | However, if you cease all violation of this License, then your
416 | license from a particular copyright holder is reinstated (a)
417 | provisionally, unless and until the copyright holder explicitly and
418 | finally terminates your license, and (b) permanently, if the copyright
419 | holder fails to notify you of the violation by some reasonable means
420 | prior to 60 days after the cessation.
421 |
422 | Moreover, your license from a particular copyright holder is
423 | reinstated permanently if the copyright holder notifies you of the
424 | violation by some reasonable means, this is the first time you have
425 | received notice of violation of this License (for any work) from that
426 | copyright holder, and you cure the violation prior to 30 days after
427 | your receipt of the notice.
428 |
429 | Termination of your rights under this section does not terminate the
430 | licenses of parties who have received copies or rights from you under
431 | this License. If your rights have been terminated and not permanently
432 | reinstated, you do not qualify to receive new licenses for the same
433 | material under section 10.
434 |
435 | 9. Acceptance Not Required for Having Copies.
436 |
437 | You are not required to accept this License in order to receive or
438 | run a copy of the Program. Ancillary propagation of a covered work
439 | occurring solely as a consequence of using peer-to-peer transmission
440 | to receive a copy likewise does not require acceptance. However,
441 | nothing other than this License grants you permission to propagate or
442 | modify any covered work. These actions infringe copyright if you do
443 | not accept this License. Therefore, by modifying or propagating a
444 | covered work, you indicate your acceptance of this License to do so.
445 |
446 | 10. Automatic Licensing of Downstream Recipients.
447 |
448 | Each time you convey a covered work, the recipient automatically
449 | receives a license from the original licensors, to run, modify and
450 | propagate that work, subject to this License. You are not responsible
451 | for enforcing compliance by third parties with this License.
452 |
453 | An "entity transaction" is a transaction transferring control of an
454 | organization, or substantially all assets of one, or subdividing an
455 | organization, or merging organizations. If propagation of a covered
456 | work results from an entity transaction, each party to that
457 | transaction who receives a copy of the work also receives whatever
458 | licenses to the work the party's predecessor in interest had or could
459 | give under the previous paragraph, plus a right to possession of the
460 | Corresponding Source of the work from the predecessor in interest, if
461 | the predecessor has it or can get it with reasonable efforts.
462 |
463 | You may not impose any further restrictions on the exercise of the
464 | rights granted or affirmed under this License. For example, you may
465 | not impose a license fee, royalty, or other charge for exercise of
466 | rights granted under this License, and you may not initiate litigation
467 | (including a cross-claim or counterclaim in a lawsuit) alleging that
468 | any patent claim is infringed by making, using, selling, offering for
469 | sale, or importing the Program or any portion of it.
470 |
471 | 11. Patents.
472 |
473 | A "contributor" is a copyright holder who authorizes use under this
474 | License of the Program or a work on which the Program is based. The
475 | work thus licensed is called the contributor's "contributor version".
476 |
477 | A contributor's "essential patent claims" are all patent claims
478 | owned or controlled by the contributor, whether already acquired or
479 | hereafter acquired, that would be infringed by some manner, permitted
480 | by this License, of making, using, or selling its contributor version,
481 | but do not include claims that would be infringed only as a
482 | consequence of further modification of the contributor version. For
483 | purposes of this definition, "control" includes the right to grant
484 | patent sublicenses in a manner consistent with the requirements of
485 | this License.
486 |
487 | Each contributor grants you a non-exclusive, worldwide, royalty-free
488 | patent license under the contributor's essential patent claims, to
489 | make, use, sell, offer for sale, import and otherwise run, modify and
490 | propagate the contents of its contributor version.
491 |
492 | In the following three paragraphs, a "patent license" is any express
493 | agreement or commitment, however denominated, not to enforce a patent
494 | (such as an express permission to practice a patent or covenant not to
495 | sue for patent infringement). To "grant" such a patent license to a
496 | party means to make such an agreement or commitment not to enforce a
497 | patent against the party.
498 |
499 | If you convey a covered work, knowingly relying on a patent license,
500 | and the Corresponding Source of the work is not available for anyone
501 | to copy, free of charge and under the terms of this License, through a
502 | publicly available network server or other readily accessible means,
503 | then you must either (1) cause the Corresponding Source to be so
504 | available, or (2) arrange to deprive yourself of the benefit of the
505 | patent license for this particular work, or (3) arrange, in a manner
506 | consistent with the requirements of this License, to extend the patent
507 | license to downstream recipients. "Knowingly relying" means you have
508 | actual knowledge that, but for the patent license, your conveying the
509 | covered work in a country, or your recipient's use of the covered work
510 | in a country, would infringe one or more identifiable patents in that
511 | country that you have reason to believe are valid.
512 |
513 | If, pursuant to or in connection with a single transaction or
514 | arrangement, you convey, or propagate by procuring conveyance of, a
515 | covered work, and grant a patent license to some of the parties
516 | receiving the covered work authorizing them to use, propagate, modify
517 | or convey a specific copy of the covered work, then the patent license
518 | you grant is automatically extended to all recipients of the covered
519 | work and works based on it.
520 |
521 | A patent license is "discriminatory" if it does not include within
522 | the scope of its coverage, prohibits the exercise of, or is
523 | conditioned on the non-exercise of one or more of the rights that are
524 | specifically granted under this License. You may not convey a covered
525 | work if you are a party to an arrangement with a third party that is
526 | in the business of distributing software, under which you make payment
527 | to the third party based on the extent of your activity of conveying
528 | the work, and under which the third party grants, to any of the
529 | parties who would receive the covered work from you, a discriminatory
530 | patent license (a) in connection with copies of the covered work
531 | conveyed by you (or copies made from those copies), or (b) primarily
532 | for and in connection with specific products or compilations that
533 | contain the covered work, unless you entered into that arrangement,
534 | or that patent license was granted, prior to 28 March 2007.
535 |
536 | Nothing in this License shall be construed as excluding or limiting
537 | any implied license or other defenses to infringement that may
538 | otherwise be available to you under applicable patent law.
539 |
540 | 12. No Surrender of Others' Freedom.
541 |
542 | If conditions are imposed on you (whether by court order, agreement or
543 | otherwise) that contradict the conditions of this License, they do not
544 | excuse you from the conditions of this License. If you cannot convey a
545 | covered work so as to satisfy simultaneously your obligations under this
546 | License and any other pertinent obligations, then as a consequence you may
547 | not convey it at all. For example, if you agree to terms that obligate you
548 | to collect a royalty for further conveying from those to whom you convey
549 | the Program, the only way you could satisfy both those terms and this
550 | License would be to refrain entirely from conveying the Program.
551 |
552 | 13. Use with the GNU Affero General Public License.
553 |
554 | Notwithstanding any other provision of this License, you have
555 | permission to link or combine any covered work with a work licensed
556 | under version 3 of the GNU Affero General Public License into a single
557 | combined work, and to convey the resulting work. The terms of this
558 | License will continue to apply to the part which is the covered work,
559 | but the special requirements of the GNU Affero General Public License,
560 | section 13, concerning interaction through a network will apply to the
561 | combination as such.
562 |
563 | 14. Revised Versions of this License.
564 |
565 | The Free Software Foundation may publish revised and/or new versions of
566 | the GNU General Public License from time to time. Such new versions will
567 | be similar in spirit to the present version, but may differ in detail to
568 | address new problems or concerns.
569 |
570 | Each version is given a distinguishing version number. If the
571 | Program specifies that a certain numbered version of the GNU General
572 | Public License "or any later version" applies to it, you have the
573 | option of following the terms and conditions either of that numbered
574 | version or of any later version published by the Free Software
575 | Foundation. If the Program does not specify a version number of the
576 | GNU General Public License, you may choose any version ever published
577 | by the Free Software Foundation.
578 |
579 | If the Program specifies that a proxy can decide which future
580 | versions of the GNU General Public License can be used, that proxy's
581 | public statement of acceptance of a version permanently authorizes you
582 | to choose that version for the Program.
583 |
584 | Later license versions may give you additional or different
585 | permissions. However, no additional obligations are imposed on any
586 | author or copyright holder as a result of your choosing to follow a
587 | later version.
588 |
589 | 15. Disclaimer of Warranty.
590 |
591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
599 |
600 | 16. Limitation of Liability.
601 |
602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
610 | SUCH DAMAGES.
611 |
612 | 17. Interpretation of Sections 15 and 16.
613 |
614 | If the disclaimer of warranty and limitation of liability provided
615 | above cannot be given local legal effect according to their terms,
616 | reviewing courts shall apply local law that most closely approximates
617 | an absolute waiver of all civil liability in connection with the
618 | Program, unless a warranty or assumption of liability accompanies a
619 | copy of the Program in return for a fee.
620 |
621 | END OF TERMS AND CONDITIONS
622 |
623 | How to Apply These Terms to Your New Programs
624 |
625 | If you develop a new program, and you want it to be of the greatest
626 | possible use to the public, the best way to achieve this is to make it
627 | free software which everyone can redistribute and change under these terms.
628 |
629 | To do so, attach the following notices to the program. It is safest
630 | to attach them to the start of each source file to most effectively
631 | state the exclusion of warranty; and each file should have at least
632 | the "copyright" line and a pointer to where the full notice is found.
633 |
634 |
635 | Copyright (C)
636 |
637 | This program is free software: you can redistribute it and/or modify
638 | it under the terms of the GNU General Public License as published by
639 | the Free Software Foundation, either version 3 of the License, or
640 | (at your option) any later version.
641 |
642 | This program is distributed in the hope that it will be useful,
643 | but WITHOUT ANY WARRANTY; without even the implied warranty of
644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
645 | GNU General Public License for more details.
646 |
647 | You should have received a copy of the GNU General Public License
648 | along with this program. If not, see .
649 |
650 | Also add information on how to contact you by electronic and paper mail.
651 |
652 | If the program does terminal interaction, make it output a short
653 | notice like this when it starts in an interactive mode:
654 |
655 | Copyright (C)
656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
657 | This is free software, and you are welcome to redistribute it
658 | under certain conditions; type `show c' for details.
659 |
660 | The hypothetical commands `show w' and `show c' should show the appropriate
661 | parts of the General Public License. Of course, your program's commands
662 | might be different; for a GUI interface, you would use an "about box".
663 |
664 | You should also get your employer (if you work as a programmer) or school,
665 | if any, to sign a "copyright disclaimer" for the program, if necessary.
666 | For more information on this, and how to apply and follow the GNU GPL, see
667 | .
668 |
669 | The GNU General Public License does not permit incorporating your program
670 | into proprietary programs. If your program is a subroutine library, you
671 | may consider it more useful to permit linking proprietary applications with
672 | the library. If this is what you want to do, use the GNU Lesser General
673 | Public License instead of this License. But first, please read
674 | .
675 |
--------------------------------------------------------------------------------