diff --git a/core/core-awt/src/main/java/org/icepdf/core/pobjects/PDate.java b/core/core-awt/src/main/java/org/icepdf/core/pobjects/PDate.java index aa228e3bb..6e48fe721 100644 --- a/core/core-awt/src/main/java/org/icepdf/core/pobjects/PDate.java +++ b/core/core-awt/src/main/java/org/icepdf/core/pobjects/PDate.java @@ -19,6 +19,7 @@ import java.text.SimpleDateFormat; import java.time.LocalDateTime; +import java.time.ZoneId; import java.util.*; /** @@ -356,6 +357,10 @@ public LocalDateTime asLocalDateTime() { return LocalDateTime.of(y, m, d, h, min, sec); } + public long toEpochSecond() { + return asLocalDateTime().atZone(ZoneId.systemDefault()).toEpochSecond(); + } + /** * Utility mehtod for parsing Adobe standard date format, * (D:YYYYMMDDHHmmSSOHH'mm'). diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/SwingController.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/SwingController.java index adf0f96d5..32953bfdc 100644 --- a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/SwingController.java +++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/SwingController.java @@ -5086,7 +5086,7 @@ public void actionPerformed(ActionEvent event) { "viewer.dialog.error.exception.msg", message); SwingUtilities.invokeLater(doSwingWork); - logger.log(Level.FINE, "Error processing action event.", e); + logger.log(Level.SEVERE, "Error processing action event.", e); } if (!cancelSetFocus) { @@ -5760,7 +5760,7 @@ public void propertyChange(PropertyChangeEvent evt) { } if (annotationSummaryFrame != null && annotationSummaryFrame.getAnnotationSummaryPanel() != null) { - annotationSummaryFrame.getAnnotationSummaryPanel().refreshDocumentInstance(); + annotationSummaryFrame.refreshDocumentInstance(); } break; case PropertyConstants.DESTINATION_ADDED: diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/utility/annotation/markup/FindMarkupAnnotationTask.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/utility/annotation/markup/FindMarkupAnnotationTask.java index 923470d53..d1552ca07 100644 --- a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/utility/annotation/markup/FindMarkupAnnotationTask.java +++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/utility/annotation/markup/FindMarkupAnnotationTask.java @@ -319,11 +319,9 @@ private void checkGroupLabelChange(MarkupAnnotationPanel.SortColumn sortColumn, } private String findColor(Color color) { - if (colorLabels != null) { - for (DragDropColorList.ColorLabel colorLabel : colorLabels) { - if (color.equals(colorLabel.getColor())) { - return colorLabel.getLabel(); - } + for (DragDropColorList.ColorLabel colorLabel : colorLabels) { + if (color.equals(colorLabel.getColor())) { + return colorLabel.getLabel(); } } // see if we can't return a hex color diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/utility/annotation/markup/MarkupAnnotationPanel.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/utility/annotation/markup/MarkupAnnotationPanel.java index a5f8af964..211ee383c 100644 --- a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/utility/annotation/markup/MarkupAnnotationPanel.java +++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/utility/annotation/markup/MarkupAnnotationPanel.java @@ -310,12 +310,10 @@ protected void applyAnnotationStatusLabel(Annotation annotation) { ArrayList colorLabels = DragDropColorList.retrieveColorLabels(); String colorLabelString = null; - if (colorLabels != null) { - for (DragDropColorList.ColorLabel colorLabel : colorLabels) { - if (annotation.getColor() != null && colorLabel.getColor().equals(annotation.getColor())) { - colorLabelString = colorLabel.getLabel(); - break; - } + for (DragDropColorList.ColorLabel colorLabel : colorLabels) { + if (annotation.getColor() != null && colorLabel.getColor().equals(annotation.getColor())) { + colorLabelString = colorLabel.getLabel(); + break; } } StringBuilder statusLabel = new StringBuilder(); diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/Controller.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/Controller.java index d98809dca..23815a68c 100644 --- a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/Controller.java +++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/Controller.java @@ -26,6 +26,7 @@ import java.awt.*; import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; import java.io.InputStream; import java.net.URL; import java.util.ResourceBundle; diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/DocumentViewController.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/DocumentViewController.java index 9eba22d37..766f0f318 100644 --- a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/DocumentViewController.java +++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/DocumentViewController.java @@ -18,13 +18,12 @@ import org.icepdf.core.SecurityCallback; import org.icepdf.core.pobjects.Destination; import org.icepdf.core.pobjects.Document; -import org.icepdf.ri.common.views.annotations.AbstractAnnotationComponent; import javax.swing.*; import java.awt.*; import java.awt.event.KeyListener; +import java.beans.PropertyChangeSupport; import java.util.Collection; -import java.util.Set; /** @@ -234,5 +233,10 @@ public interface DocumentViewController { void firePropertyChange(String event, Object oldValue, Object newValue); + /** + * @return The property change support for this controller + */ + PropertyChangeSupport getPropertyChangeSupport(); + void deleteAnnotations(Collection annotations); } diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/DocumentViewControllerImpl.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/DocumentViewControllerImpl.java index 6bd4ad8ab..7d8b9d67e 100644 --- a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/DocumentViewControllerImpl.java +++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/DocumentViewControllerImpl.java @@ -1302,6 +1302,11 @@ public void firePropertyChange(String event, Object oldValue, changes.firePropertyChange(event, oldValue, newValue); } + @Override + public PropertyChangeSupport getPropertyChangeSupport() { + return changes; + } + public void addPropertyChangeListener(PropertyChangeListener l) { changes.addPropertyChangeListener(l); } diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/PopupAnnotationComponent.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/PopupAnnotationComponent.java index 16c1c8aa5..5d30defff 100644 --- a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/PopupAnnotationComponent.java +++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/PopupAnnotationComponent.java @@ -87,6 +87,7 @@ public class PopupAnnotationComponent extends AbstractAnnotationComponent commentTree.removeMouseListener(ml)); + this.summaryController = summaryController; - setFocusable(false); removeFocusListener(this); + setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR)); commentPanel.setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR)); // hides a bunch of the controls. commentPanel.removeMouseListener(popupListener); @@ -58,30 +87,91 @@ public AnnotationSummaryBox(PopupAnnotation annotation, DocumentViewController d privateToggleButton.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); minimizeButton.setVisible(false); - textArea.setEditable(true); + textArea.setEditable(isCurrentUserOwner(selectedMarkupAnnotation)); textArea.setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR)); commentPanel.getInsets().set(10, 10, 10, 10); setBorder(BorderFactory.createLineBorder(Color.LIGHT_GRAY)); - ViewerPropertiesManager propertiesManager = documentViewController.getParentController().getPropertiesManager(); + final ViewerPropertiesManager propertiesManager = documentViewController.getParentController().getPropertiesManager(); setFontSize(propertiesManager.getPreferences().getInt( ViewerPropertiesManager.PROPERTY_ANNOTATION_SUMMARY_FONT_SIZE, new JLabel().getFont().getSize())); // remove super mouse listener as it interferes with the drag and drop. removeMouseWheelListener(this); + this.reference = getAnnotationComponent().getAnnotation().getPObjectReference(); + this.componentAnnotation = getAnnotationComponent().getAnnotation(); + setRequestFocusEnabled(true); + setFocusable(true); + privateToggleButton.addMouseListener(new MouseAdapter() { + + @Override + public void mouseEntered(final MouseEvent e) { + repaint(); + } + + @Override + public void mouseExited(final MouseEvent e) { + repaint(); + } + + }); + resetComponentColors(); + } + + + public void setTextBlockVisibility(final boolean visible) { + showTextBlock = visible; + textArea.setVisible(showTextBlock); + validate(); } public void toggleTextBlockVisibility() { showTextBlock = !showTextBlock; - textArea.setVisible(showTextBlock); + setTextBlockVisibility(showTextBlock); } + @Override public boolean isShowTextBlockVisible() { return showTextBlock; } - protected void updateContent(DocumentEvent e) { + @Override + public void setBounds(final int x, final int y, final int width, final int height) { + final Rectangle boundRectangle = limitAnnotationPosition(x, y, width, height); + super.setBounds(boundRectangle.x, boundRectangle.y, boundRectangle.width, boundRectangle.height); + } + + @Override + public void toggleHeaderVisibility() { + showHeader = !showHeader; + setHeaderVisibility(showHeader); + } + + @Override + public void setHeaderVisibility(final boolean visible) { + showHeader = visible; + titleLabel.setVisible(showHeader); + creationLabel.setVisible(showHeader); + privateToggleButton.setVisible(SystemProperties.PRIVATE_PROPERTY_ENABLED && showHeader); + setCorrectBorder(); + validate(); + } + + @Override + public boolean isHeaderVisible() { + return showHeader; + } + + @Override + protected void resetComponentColors() { + final Color color = getColor(); + popupBackgroundColor = new Color(color.getRed(), color.getGreen(), color.getBlue(), TextMarkupAnnotation.HIGHLIGHT_ALPHA); + super.resetComponentColors(); + commentPanel.setBackground(popupBackgroundColor); + } + + protected void updateContent(final DocumentEvent e) { // get the next text and save it to the selected markup annotation. - Document document = e.getDocument(); + final Document document = e.getDocument(); try { if (document.getLength() > 0) { selectedMarkupAnnotation.setModifiedDate(PDate.formatDateTime(new Date())); @@ -90,44 +180,91 @@ protected void updateContent(DocumentEvent e) { // add them to the container, using absolute positioning. documentViewController.updatedSummaryAnnotation(this); } - } catch (BadLocationException ex) { + } catch (final BadLocationException ex) { logger.log(Level.FINE, "Error updating markup annotation content", ex); } } @Override - public void actionPerformed(ActionEvent e) { - Object source = e.getSource(); + public void actionPerformed(final ActionEvent e) { + final Object source = e.getSource(); if (source == null) return; if (source == privateToggleButton) { - boolean selected = privateToggleButton.isSelected(); - MarkupAnnotation markupAnnotation = annotation.getParent(); - if (markupAnnotation != null) { - markupAnnotation.setFlag(Annotation.FLAG_PRIVATE_CONTENTS, selected); - markupAnnotation.setModifiedDate(PDate.formatDateTime(new Date())); + final boolean selected = privateToggleButton.isSelected(); + if (selectedMarkupAnnotation != null) { + selectedMarkupAnnotation.setFlag(Annotation.FLAG_PRIVATE_CONTENTS, selected); + selectedMarkupAnnotation.setModifiedDate(PDate.formatDateTime(new Date())); documentViewController.updatedSummaryAnnotation(this); } + repaint(); + } + } + + @Override + public Color getColor() { + if (getAnnotationComponent() == null || + getAnnotationComponent().getAnnotation() == null || + getAnnotationComponent().getAnnotation().getColor() == null) { + return popupBackgroundColor; + } else { + return getAnnotationComponent().getAnnotation().getColor(); + } + } + + @Override + public void moveTo(final Color c, final boolean isTopComponent) { + final Annotation annot = getAnnotationComponent().getAnnotation(); + final Color oldColor = annot.getColor(); + annot.setColor(c); + annot.getPage().updateAnnotation(annot); + resetComponentColors(); + summaryController.getController().getDocumentViewController().updateAnnotation(getAnnotationComponent()); + if (isTopComponent) { + summaryController.moveTo(this, c, oldColor); + } + getAnnotationComponent().resetAppearanceShapes(); + getAnnotationComponent().repaint(); + refreshBorders(); + repaint(); + summaryController.getController().getDocumentViewController().updatedSummaryAnnotation(this); + } + + /** + * Updates the column of the component + */ + public void moveToCorrectPanel() { + final Color newColor = getColor(); + if (!popupBackgroundColor.equals(newColor)) { + summaryController.moveTo(this, newColor, popupBackgroundColor); + resetComponentColors(); } + refreshBorders(); + repaint(); } public Controller getController() { return documentViewController.getParentController(); } - public JPopupMenu getContextMenu(Frame frame, DraggableAnnotationPanel.MouseHandler mouseHandler) { + public JPopupMenu getContextMenu(final Frame frame, final DraggablePanelController panel) { + final MarkupAnnotationComponent comp = getAnnotationComponent(); + return BoxMenuFactory.createBoxPopupMenu(this, comp, frame, panel, summaryController); + } + + private MarkupAnnotationComponent getAnnotationComponent() { MarkupAnnotationComponent comp = (MarkupAnnotationComponent) getAnnotationParentComponent(); // page may not have been initialized and thus we don't have a component if (comp == null) { - int pageIndex = annotation.getParent().getPageIndex(); - PageViewComponentImpl pageViewComponent = (PageViewComponentImpl) + final int pageIndex = annotation.getParent().getPageIndex(); + final PageViewComponentImpl pageViewComponent = (PageViewComponentImpl) documentViewController.getParentController().getDocumentViewController() .getDocumentViewModel().getPageComponents().get(pageIndex); pageViewComponent.refreshAnnotationComponents(pageViewComponent.getPage(), false); - ArrayList comps = pageViewComponent.getAnnotationComponents(); - for (AbstractAnnotationComponent abstractComp : comps) { - Annotation pageAnnotation = abstractComp.getAnnotation(); - Annotation parent = annotation.getParent(); + final ArrayList comps = pageViewComponent.getAnnotationComponents(); + for (final AbstractAnnotationComponent abstractComp : comps) { + final Annotation pageAnnotation = abstractComp.getAnnotation(); + final Annotation parent = annotation.getParent(); if (pageAnnotation != null && parent != null && pageAnnotation.getPObjectReference().equals(parent.getPObjectReference())) { comp = (MarkupAnnotationComponent) abstractComp; @@ -135,9 +272,121 @@ public JPopupMenu getContextMenu(Frame frame, DraggableAnnotationPanel.MouseHand } } } - return new SummaryPopupMenu(this, (MarkupAnnotation) comp.getAnnotation(), comp, - documentViewController.getParentController(), - frame, mouseHandler); + return comp; + } + + @Override + public boolean delete() { + if (isCurrentUserOwner(annotation.getParent())) { + documentViewController.deleteAnnotation(getAnnotationComponent()); + Arrays.stream(getPropertyChangeListeners()).forEach(this::removePropertyChangeListener); + summaryController.getGroupManager().removeFromGroup(this); + summaryController.getDragAndLinkManager().unlinkComponent(this, false); + return true; + } else { + return false; + } } -} + @Override + public void refresh() { + refreshBorders(); + refreshPopupState(); + validate(); + repaint(); + } + + public void refreshBorders() { + unselectedHeaderBorder = BorderFactory.createLineBorder(PopupAnnotation.BORDER_COLOR); + unselectedNoHeaderBorder = BorderFactory.createMatteBorder(10, 1, 1, 1, getColor()); + selectedNoHeaderBorder = BorderFactory.createCompoundBorder(SELECTED_BORDER, unselectedNoHeaderBorder); + setCorrectBorder(); + } + + @Override + public Collection getAnnotations() { + return Collections.singletonList(componentAnnotation); + } + + @Override + public Collection getAnnotationComponents() { + return Collections.singletonList(getAnnotationComponent()); + } + + @Override + public void setComponentSelected(final boolean b) { + isComponentSelected = b; + setCorrectBorder(); + } + + private void setCorrectBorder() { + if (showHeader) { + if (isComponentSelected) { + setBorder(SELECTED_BORDER); + } else { + setBorder(unselectedHeaderBorder); + } + } else { + if (isComponentSelected) { + setBorder(selectedNoHeaderBorder); + } else { + setBorder(unselectedNoHeaderBorder); + } + } + } + + @Override + public boolean isComponentSelected() { + return isComponentSelected; + } + + @Override + public int hashCode() { + return reference.hashCode(); + } + + @Override + public boolean equals(final Object that) { + return that instanceof AnnotationSummaryBox && reference.equals(((AnnotationSummaryBox) that).reference); + } + + @Override + public String getDebuggable() { + return reference.toString(); + } + + @Override + public void setFontSize(final float size) { + titleLabel.setFont(titleLabel.getFont().deriveFont(size)); + creationLabel.setFont(titleLabel.getFont().deriveFont(size)); + textArea.setFont(titleLabel.getFont().deriveFont(size)); + } + + @Override + public void fireComponentMoved(final boolean snap, final boolean check, final UUID uuid) { + summaryController.getDragAndLinkManager().componentMoved(this, snap, check, uuid); + repaint(); + } + + @Override + public Component asComponent() { + return this; + } + + @Override + public Container asContainer() { + return this; + } + + public boolean canEdit() { + return isCurrentUserOwner(); + } + + private boolean isCurrentUserOwner() { + return ((MarkupAnnotation) getMarkupAnnotationComponent().getAnnotation()).getTitleText().equals(SystemProperties.USER_NAME); + } + + private static boolean isCurrentUserOwner(final MarkupAnnotation annotation) { + return annotation.getTitleText().equals(SystemProperties.USER_NAME); + } +} \ No newline at end of file diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/AnnotationSummaryComponent.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/AnnotationSummaryComponent.java new file mode 100644 index 000000000..14830423e --- /dev/null +++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/AnnotationSummaryComponent.java @@ -0,0 +1,155 @@ +package org.icepdf.ri.common.views.annotations.summary; + +import org.icepdf.core.pobjects.annotations.Annotation; +import org.icepdf.ri.common.views.AnnotationComponent; +import org.icepdf.ri.common.views.annotations.summary.colorpanel.DraggablePanelController; + +import javax.swing.*; +import javax.swing.border.Border; +import java.awt.*; +import java.util.Collection; +import java.util.UUID; + +/** + * Interface of a visible component in the summary panel + */ +public interface AnnotationSummaryComponent { + Color SELECTED_COLOR = new Color(20, 20, 150); + Border SELECTED_BORDER = BorderFactory.createLineBorder(SELECTED_COLOR, 2, true); + + /** + * Toggles the text block visibility + */ + void toggleTextBlockVisibility(); + + /** + * Sets the text block visibility + * + * @param visible true or false + */ + void setTextBlockVisibility(boolean visible); + + /** + * @return if the text block is visible + */ + boolean isShowTextBlockVisible(); + + /** + * Toggles the header visibility + */ + void toggleHeaderVisibility(); + + /** + * Sets the header visibility + * + * @param visible true or false + */ + void setHeaderVisibility(boolean visible); + + /** + * @return if the header is visible + */ + boolean isHeaderVisible(); + + /** + * Returns the context menu (right-click) for this component + * + * @param frame The parent frame + * @param panel The Panel + * @return a JPopupMenu + */ + JPopupMenu getContextMenu(Frame frame, DraggablePanelController panel); + + /** + * @return This component's color + */ + Color getColor(); + + /** + * Moves this component to another color + * + * @param c The new color + * @param isTopComponent If the method has been called on this component explicitly (false if it was called on its parent) + */ + void moveTo(Color c, boolean isTopComponent); + + /** + * Sets if the component is selected + * + * @param b true or false + */ + void setComponentSelected(boolean b); + + /** + * @return if the component is selected + */ + boolean isComponentSelected(); + + /** + * Deletes the component (and its annotations) + * + * @return if the component has been deleted + */ + boolean delete(); + + /** + * Refreshes the component + */ + void refresh(); + + /** + * @return the annotations contained by this component + */ + Collection getAnnotations(); + + /** + * @return the annotation components contained by this component + */ + Collection getAnnotationComponents(); + + @Override + int hashCode(); + + @Override + boolean equals(Object that); + + /** + * @return a debuggable string to more easily recognize this component + */ + String getDebuggable(); + + /** + * Sets the font size + * + * @param size The new font size + */ + void setFontSize(float size); + + /** + * Sets the font family + * + * @param family The font + */ + void setFontFamily(String family); + + void fireComponentMoved(boolean snap, boolean check, UUID uuid); + + /** + * Casts this object to Component + * + * @return this as a Component + */ + Component asComponent(); + + /** + * Casts this object to Container + * + * @return this as a Container + */ + Container asContainer(); + + /** + * @return If the component can be edited by the current user (i. e. the user is the creator of all the components in the component) + */ + boolean canEdit(); +} diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/AnnotationSummaryFrame.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/AnnotationSummaryFrame.java index 1930cb52c..8a3a00ecb 100644 --- a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/AnnotationSummaryFrame.java +++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/AnnotationSummaryFrame.java @@ -15,12 +15,14 @@ */ package org.icepdf.ri.common.views.annotations.summary; -import org.icepdf.core.pobjects.Document; import org.icepdf.ri.common.MutableDocument; import org.icepdf.ri.common.views.Controller; +import org.icepdf.ri.common.views.annotations.summary.mainpanel.AnnotationSummaryPanel; import org.icepdf.ri.images.Images; import javax.swing.*; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; import java.text.MessageFormat; import java.util.ResourceBundle; @@ -30,39 +32,62 @@ public class AnnotationSummaryFrame extends JFrame implements MutableDocument { protected final ResourceBundle messageBundle; protected AnnotationSummaryPanel annotationSummaryPanel; - public AnnotationSummaryFrame(Controller controller) { + public AnnotationSummaryFrame(final Controller controller) { this.controller = controller; messageBundle = controller.getMessageBundle(); - setIconImage(new ImageIcon(Images.get("icepdf-app-icon-64x64.png")).getImage()); setTitle(messageBundle.getString("viewer.window.annotationSummary.title.default")); + addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(final WindowEvent e) { + saveChanges(); + } + }); + } + + public void saveChanges() { + if (annotationSummaryPanel.getController().hasChanged() && annotationSummaryPanel.getController().canSave()) { + final MessageFormat formatter = new MessageFormat( + messageBundle.getString("viewer.summary.dialog.saveOnClose.noUpdates.msg").replace("'", "''")); + final String dialogMessage = formatter.format(new Object[]{controller.getDocument().getDocumentOrigin()}); + + final int res = JOptionPane.showConfirmDialog(this, + dialogMessage, + messageBundle.getString("viewer.summary.dialog.saveOnClose.noUpdates.title"), + JOptionPane.YES_NO_OPTION); + if (res == JOptionPane.OK_OPTION) { + annotationSummaryPanel.getController().save(); + } + annotationSummaryPanel.getController().setHasManuallyChanged(false); + } } @Override public void refreshDocumentInstance() { - Document document = controller.getDocument(); - if (document != null) { - String title = null; - if (document.getInfo() != null) { - title = document.getInfo().getTitle(); - } - Object[] messageArguments = {title != null ? title : controller.getViewerFrame().getTitle()}; - MessageFormat formatter = new MessageFormat( + if (controller.getDocument() != null) { + final Object[] messageArguments = {controller.getDocument().getDocumentOrigin()}; + final MessageFormat formatter = new MessageFormat( messageBundle.getString("viewer.window.annotationSummary.title.open.default")); setTitle(formatter.format(messageArguments)); - - annotationSummaryPanel = new AnnotationSummaryPanel(this, controller); + getContentPane().removeAll(); + annotationSummaryPanel = createAnnotationSummaryPanel(controller); getContentPane().add(annotationSummaryPanel); - annotationSummaryPanel.refreshDocumentInstance(); + annotationSummaryPanel.getController().refreshDocumentInstance(); } } + protected AnnotationSummaryPanel createAnnotationSummaryPanel(final Controller controller) { + return new AnnotationSummaryPanel(this, controller); + } + public AnnotationSummaryPanel getAnnotationSummaryPanel() { return annotationSummaryPanel; } + @Override public void disposeDocument() { + saveChanges(); getContentPane().removeAll(); invalidate(); revalidate(); diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/AnnotationSummaryGroup.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/AnnotationSummaryGroup.java new file mode 100644 index 000000000..51f8f9c03 --- /dev/null +++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/AnnotationSummaryGroup.java @@ -0,0 +1,322 @@ +package org.icepdf.ri.common.views.annotations.summary; + +import org.icepdf.core.pobjects.annotations.Annotation; +import org.icepdf.ri.common.views.AnnotationComponent; +import org.icepdf.ri.common.views.annotations.summary.colorpanel.DraggablePanelController; +import org.icepdf.ri.common.views.annotations.summary.mainpanel.SummaryController; +import org.icepdf.ri.common.views.annotations.summary.menu.GroupMenuFactory; + +import javax.swing.*; +import javax.swing.border.CompoundBorder; +import javax.swing.border.TitledBorder; +import java.awt.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.UUID; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import static java.util.Objects.requireNonNull; + +/** + * Represents a group of AnnotationSummaryComponent + */ +public class AnnotationSummaryGroup extends MoveableComponentsPanel implements AnnotationSummaryComponent { + + private List components; + private String name; + private TitledBorder border; + private CompoundBorder groupSelectedBorder; + private final SummaryController summaryController; + private boolean isSelected = false; + private Color color; + private final UUID id; + + public AnnotationSummaryGroup(final Collection components, final String name, final SummaryController summaryController) { + setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); + this.components = new ArrayList<>(components); + this.name = requireNonNull(name); + this.summaryController = requireNonNull(summaryController); + this.id = UUID.randomUUID(); + setRequestFocusEnabled(true); + setFocusable(true); + getColor(); + refreshBorder(); + refreshComponents(); + } + + public Color getColor() { + if (summaryController.isSingleDefaultColor()) { + color = summaryController.getAnnotationNamedColorPanels().get(0).getColorLabel().getColor(); + } else if (!components.isEmpty()) { + color = components.get(0).getColor(); + } + return color; + } + + @Override + public void moveTo(final Color c, final boolean isTopComponent) { + final Color oldColor = getColor(); + components.forEach(a -> a.moveTo(c, false)); + refreshColor(); + summaryController.getGroupManager().moveTo(this, oldColor, c); + if (isTopComponent) { + summaryController.moveTo(this, c, oldColor); + } + } + + /** + * Clears the group + */ + public void clear() { + removeAll(); + components.clear(); + } + + @Override + public void setComponentSelected(final boolean b) { + isSelected = b; + setBorder(b ? groupSelectedBorder : border); + } + + @Override + public boolean isComponentSelected() { + return isSelected; + } + + /** + * @return all the names of the groups contained in this group recursively + */ + public Set getAllSubnames() { + return getAllSubnames(this, new HashSet<>()); + } + + private Set getAllSubnames(final AnnotationSummaryGroup g, final Set names) { + g.getSubComponents().forEach(subComp -> { + if (subComp instanceof AnnotationSummaryGroup) { + getAllSubnames((AnnotationSummaryGroup) subComp, names); + } + }); + names.add(g.name); + return names; + } + + @Override + public boolean delete() { + int undeletable = 0; + while (!components.isEmpty() && undeletable != components.size()) { + summaryController.getDragAndLinkManager().unlinkComponent(components.get(undeletable), false); + if (!components.get(undeletable).delete()) { + undeletable++; + } + } + Arrays.stream(getPropertyChangeListeners()).forEach(this::removePropertyChangeListener); + summaryController.getGroupManager().disbandGroup(this); + return true; + } + + @Override + public void refresh() { + components.forEach(AnnotationSummaryComponent::refresh); + refreshComponents(); + refreshBorder(); + } + + @Override + public Collection getAnnotations() { + return components.stream().flatMap(c -> c.getAnnotations().stream()).collect(Collectors.toList()); + } + + @Override + public Collection getAnnotationComponents() { + return components.stream().flatMap(c -> c.getAnnotationComponents().stream()).collect(Collectors.toList()); + } + + + private void refreshBorder() { + this.border = BorderFactory.createTitledBorder(BorderFactory.createLineBorder(Color.BLACK, 2, true), name, TitledBorder.LEFT, TitledBorder.TOP); + this.groupSelectedBorder = BorderFactory.createCompoundBorder(border, SELECTED_BORDER); + setComponentSelected(isSelected); + } + + private void refreshColor() { + refreshBorder(); + } + + /** + * @return The list of AnnotationSummaryComponents contained in this group + */ + public List getSubComponents() { + return Collections.unmodifiableList(components); + } + + public void setComponents(final List components) { + this.components = new ArrayList<>(components); + refreshComponents(); + } + + private void refreshComponents() { + removeAll(); + components.forEach(c -> add(c.asComponent())); + revalidate(); + repaint(); + } + + public void addComponent(final AnnotationSummaryComponent component) { + if (!components.contains(component)) { + components.add(component); + add(component.asComponent()); + } + revalidate(); + repaint(); + } + + public void insertComponent(final int idx, final AnnotationSummaryComponent component) { + if (!components.contains(component)) { + components.add(idx, component); + add(component.asComponent(), idx); + } + revalidate(); + repaint(); + } + + public void removeComponent(final AnnotationSummaryComponent component) { + components.remove(component); + if (components.isEmpty()) { + summaryController.getGroupManager().disbandGroup(this); + } + remove(component.asComponent()); + revalidate(); + repaint(); + } + + @Override + public void move(final Component component, final int idx) { + final AnnotationSummaryComponent asComp = (AnnotationSummaryComponent) component; + if (components.contains(asComp)) { + components.remove(asComp); + components.add(idx, asComp); + remove(component); + add(component, idx); + } + revalidate(); + repaint(); + } + + @Override + public String getName() { + return name; + } + + @Override + public void setName(final String name) { + this.name = name; + refreshBorder(); + } + + @Override + public void setTextBlockVisibility(final boolean visible) { + components.forEach(c -> c.setTextBlockVisibility(visible)); + validate(); + } + + @Override + public void toggleTextBlockVisibility() { + setTextBlockVisibility(!isShowTextBlockVisible()); + } + + @Override + public boolean isShowTextBlockVisible() { + return components.stream().allMatch(AnnotationSummaryComponent::isShowTextBlockVisible); + } + + @Override + public void toggleHeaderVisibility() { + components.forEach(AnnotationSummaryComponent::toggleHeaderVisibility); + } + + @Override + public void setHeaderVisibility(final boolean visible) { + components.forEach(c -> c.setHeaderVisibility(visible)); + } + + @Override + public boolean isHeaderVisible() { + return components.stream().allMatch(AnnotationSummaryComponent::isHeaderVisible); + } + + @Override + public JPopupMenu getContextMenu(final Frame frame, final DraggablePanelController panel) { + return GroupMenuFactory.createGroupPopupMenu(this, summaryController, panel); + } + + @Override + public int hashCode() { + return id.hashCode(); + } + + @Override + public boolean equals(final Object that) { + return that instanceof AnnotationSummaryGroup && this.id.equals(((AnnotationSummaryGroup) that).id); + } + + @Override + public String getDebuggable() { + return name; + } + + @Override + public void setFontSize(final float size) { + components.forEach(c -> c.setFontSize(size)); + } + + @Override + public void setFontFamily(final String family) { + components.forEach(c -> c.setFontFamily(family)); + } + + @Override + public void fireComponentMoved(final boolean snap, final boolean check, final UUID uuid) { + summaryController.getDragAndLinkManager().componentMoved(this, snap, check, uuid); + } + + @Override + public Component asComponent() { + return this; + } + + @Override + public Container asContainer() { + return this; + } + + @Override + public boolean canEdit() { + return components.stream().allMatch(AnnotationSummaryComponent::canEdit); + } + + /** + * Search the group for an annotation with the given arguments + * + * @param filter The annotation box filter + * @return The annotation if found, null otherwise + */ + public AnnotationSummaryComponent findComponentFor(final Predicate filter) { + for (final AnnotationSummaryComponent comp : getSubComponents()) { + if (comp instanceof AnnotationSummaryBox && filter.test(((AnnotationSummaryBox) comp))) { + return comp; + } else if (comp instanceof AnnotationSummaryGroup) { + final AnnotationSummaryComponent found = ((AnnotationSummaryGroup) comp).findComponentFor(filter); + if (found != null) { + return found; + } + } + } + return null; + } +} \ No newline at end of file diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/AnnotationSummaryPanel.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/AnnotationSummaryPanel.java deleted file mode 100644 index babad83b3..000000000 --- a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/AnnotationSummaryPanel.java +++ /dev/null @@ -1,471 +0,0 @@ -/* - * Copyright 2006-2019 ICEsoft Technologies Canada Corp. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the - * License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an "AS - * IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language - * governing permissions and limitations under the License. - */ -package org.icepdf.ri.common.views.annotations.summary; - -import org.icepdf.core.pobjects.Document; -import org.icepdf.core.pobjects.annotations.Annotation; -import org.icepdf.core.pobjects.annotations.MarkupAnnotation; -import org.icepdf.core.util.PropertyConstants; -import org.icepdf.ri.common.MutableDocument; -import org.icepdf.ri.common.utility.annotation.properties.FreeTextAnnotationPanel; -import org.icepdf.ri.common.utility.annotation.properties.ValueLabelItem; -import org.icepdf.ri.common.views.Controller; -import org.icepdf.ri.common.views.DocumentViewControllerImpl; -import org.icepdf.ri.common.views.annotations.MarkupAnnotationComponent; -import org.icepdf.ri.common.views.annotations.PopupAnnotationComponent; -import org.icepdf.ri.common.widgets.DragDropColorList; -import org.icepdf.ri.util.ViewerPropertiesManager; - -import javax.swing.*; -import java.awt.*; -import java.awt.event.*; -import java.beans.PropertyChangeEvent; -import java.beans.PropertyChangeListener; -import java.util.ArrayList; -import java.util.List; -import java.util.ResourceBundle; - -public class AnnotationSummaryPanel extends JPanel implements MutableDocument, PropertyChangeListener, - MouseListener, ComponentListener, ItemListener { - - protected final Frame frame; - protected final Controller controller; - protected final ResourceBundle messageBundle; - - protected MarkupAnnotation lastSelectedMarkupAnnotation; - - protected final GridBagConstraints constraints; - protected JPanel annotationsPanel; - - // font configuration - private JComboBox fontNameBox; - private JComboBox fontSizeBox; - protected JPanel statusToolbarPanel; - - private static final int DEFAULT_FONT_SIZE = 5; - private static final int DEFAULT_FONT_FAMILY = 0; - - - protected ArrayList annotationNamedColorPanels; - - public AnnotationSummaryPanel(Frame frame, Controller controller) { - this.frame = frame; - this.controller = controller; - messageBundle = controller.getMessageBundle(); - - setLayout(new BorderLayout()); - setAlignmentY(JPanel.TOP_ALIGNMENT); - setFocusable(true); - constraints = new GridBagConstraints(); - - buildStatusToolBarPanel(); - - // listen for annotations changes. - ((DocumentViewControllerImpl) controller.getDocumentViewController()).addPropertyChangeListener(this); - addComponentListener(this); - - // add key listeners for ctr, 0, -, = : reset, decrease and increase font size. - addFontSizeBindings(); - } - - @Override - public void refreshDocumentInstance() { - if (controller.getDocument() != null) { - // get the named colour and build out the draggable panels. - Document document = controller.getDocument(); - ArrayList colorLabels = DragDropColorList.retrieveColorLabels(); - int numberOfPanels = colorLabels != null ? colorLabels.size() : 1; - if (annotationNamedColorPanels != null) annotationNamedColorPanels.clear(); - annotationNamedColorPanels = new ArrayList<>(numberOfPanels); - - if (colorLabels != null && colorLabels.size() > 0) { - // build a panel for each color - for (DragDropColorList.ColorLabel colorLabel : colorLabels) { - ColorLabelPanel annotationColumnPanel = new ColorLabelPanel(frame, controller, colorLabel); - annotationColumnPanel.addPropertyChangeListener( - PropertyConstants.ANNOTATION_SUMMARY_BOX_FONT_SIZE_CHANGE, - annotationColumnPanel); - annotationNamedColorPanels.add(annotationColumnPanel); - annotationColumnPanel.addMouseListener(this); - for (int i = 0, max = document.getNumberOfPages(); i < max; i++) { - List annotations = document.getPageTree().getPage(i).getAnnotations(); - if (annotations != null) { - for (Annotation annotation : annotations) { - if (annotation instanceof MarkupAnnotation - && colorLabel.getColor().equals(annotation.getColor())) { - annotationColumnPanel.addAnnotation((MarkupAnnotation) annotation); - } - } - } - } - - } - // check to make sure a label has - } else { - // other wise just one big panel with all the named colors. - ColorLabelPanel annotationColumnPanel = new ColorLabelPanel(frame, controller, null); - annotationColumnPanel.addPropertyChangeListener( - PropertyConstants.ANNOTATION_SUMMARY_BOX_FONT_SIZE_CHANGE, - annotationColumnPanel); - annotationNamedColorPanels.add(annotationColumnPanel); - for (int i = 0, max = document.getNumberOfPages(); i < max; i++) { - List annotations = document.getPageTree().getPage(i).getAnnotations(); - if (annotations != null) { - for (Annotation annotation : annotations) { - if (annotation instanceof MarkupAnnotation) { - annotationColumnPanel.addAnnotation((MarkupAnnotation) annotation); - } - } - } - } - } - } - refreshPanelLayout(); - } - - @Override - public void itemStateChanged(ItemEvent e) { - if (e.getSource() == fontSizeBox) { - ViewerPropertiesManager propertiesManager = controller.getPropertiesManager(); - propertiesManager.getPreferences().putInt(ViewerPropertiesManager.PROPERTY_ANNOTATION_SUMMARY_FONT_SIZE, - (int) fontSizeBox.getModel().getElementAt(fontSizeBox.getSelectedIndex()).getValue()); - ValueLabelItem tmp = (ValueLabelItem) fontSizeBox.getSelectedItem(); - // fire the font size property change event. - updateSummaryFontSizes(0, (int) tmp.getValue()); - } - } - - private void updateSummaryFontSizes(int oldFontSizeIndex, int newFontSizeIndex) { - if (annotationNamedColorPanels != null) { - for (ColorLabelPanel colorLabelPanel : annotationNamedColorPanels) { - colorLabelPanel.firePropertyChange(PropertyConstants.ANNOTATION_SUMMARY_BOX_FONT_SIZE_CHANGE, - oldFontSizeIndex, newFontSizeIndex); - } - } - } - - private void addFontSizeBindings() { - InputMap inputMap = getInputMap(WHEN_FOCUSED); - ActionMap actionMap = getActionMap(); - - /// ctrl-- to increase font size. - KeyStroke key = KeyStroke.getKeyStroke(KeyEvent.VK_EQUALS, InputEvent.CTRL_DOWN_MASK); - inputMap.put(key, "font-size-increase"); - actionMap.put("font-size-increase", new AbstractAction() { - @Override - public void actionPerformed(ActionEvent e) { - if (fontSizeBox.getSelectedIndex() + 1 < fontSizeBox.getItemCount()) { - fontSizeBox.setSelectedIndex(fontSizeBox.getSelectedIndex() + 1); - } - } - }); - - // ctrl-0 to dfeault font size. - key = KeyStroke.getKeyStroke(KeyEvent.VK_0, InputEvent.CTRL_DOWN_MASK); - inputMap.put(key, "font-size-default"); - actionMap.put("font-size-default", new AbstractAction() { - @Override - public void actionPerformed(ActionEvent e) { - fontSizeBox.setSelectedIndex(DEFAULT_FONT_SIZE); - } - }); - - // ctrl-- to decrease font size. - key = KeyStroke.getKeyStroke(KeyEvent.VK_MINUS, InputEvent.CTRL_DOWN_MASK); - inputMap.put(key, "font-size-decrease"); - actionMap.put("font-size-decrease", new AbstractAction() { - @Override - public void actionPerformed(ActionEvent e) { - if (fontSizeBox.getSelectedIndex() - 1 >= 0) { - fontSizeBox.setSelectedIndex(fontSizeBox.getSelectedIndex() - 1); - } - } - }); - } - - protected void buildStatusToolBarPanel() { - - ViewerPropertiesManager propertiesManager = controller.getPropertiesManager(); - - fontSizeBox = new JComboBox<>(FreeTextAnnotationPanel.generateFontSizeNameList(messageBundle)); - applySelectedValue(fontSizeBox, propertiesManager.checkAndStoreIntProperty( - ViewerPropertiesManager.PROPERTY_ANNOTATION_SUMMARY_FONT_SIZE, new JLabel().getFont().getSize())); - fontSizeBox.addItemListener(this); - - statusToolbarPanel = new JPanel(new GridBagLayout()); - statusToolbarPanel.setAlignmentY(JPanel.TOP_ALIGNMENT); - constraints.fill = GridBagConstraints.NONE; - constraints.weightx = 0; - constraints.weighty = 1; - constraints.anchor = GridBagConstraints.WEST; - constraints.insets = new Insets(5, 5, 5, 0); - addGB(statusToolbarPanel, new JLabel(messageBundle.getString("viewer.annotationSummary.fontSize.label")), - 0, 0, 1, 1); - addGB(statusToolbarPanel, fontSizeBox, 1, 0, 1, 1); - constraints.weightx = 1; - addGB(statusToolbarPanel, new JLabel(), 2, 0, 1, 1); - } - - public void refreshPanelLayout() { - removeAll(); - - annotationsPanel = new JPanel(new GridBagLayout()); - annotationsPanel.setAlignmentY(JPanel.TOP_ALIGNMENT); - add(annotationsPanel, BorderLayout.CENTER); - add(statusToolbarPanel, BorderLayout.SOUTH); - - ArrayList colorLabels = DragDropColorList.retrieveColorLabels(); - int numberOfPanels = colorLabels != null && colorLabels.size() > 0 ? colorLabels.size() : 1; - constraints.weightx = 1.0 / (float) numberOfPanels; - constraints.weighty = 1.0f; - constraints.insets = new Insets(0, 5, 0, 0); - constraints.fill = GridBagConstraints.BOTH; - int k = 0; - for (ColorLabelPanel annotationColumnPanel : annotationNamedColorPanels) { - if (annotationColumnPanel.getNumberOfComponents() > 0) { - addGB(annotationsPanel, annotationColumnPanel, ++k, 0, 1, 1); - } - } - invalidate(); - revalidate(); - repaint(); - } - - @Override - public void propertyChange(PropertyChangeEvent evt) { - - Object newValue = evt.getNewValue(); - Object oldValue = evt.getOldValue(); - String propertyName = evt.getPropertyName(); - switch (propertyName) { - case PropertyConstants.ANNOTATION_DELETED: - if (oldValue instanceof MarkupAnnotationComponent) { - // find an remove the markup annotation node. - MarkupAnnotationComponent comp = (MarkupAnnotationComponent) oldValue; - MarkupAnnotation markupAnnotation = (MarkupAnnotation) comp.getAnnotation(); - if (annotationNamedColorPanels != null) { - ArrayList colorLabels = DragDropColorList.retrieveColorLabels(); - if (colorLabels != null) { - for (ColorLabelPanel annotationColumnPanel : annotationNamedColorPanels) { - if (annotationColumnPanel.getColorLabel() != null && - annotationColumnPanel.getColorLabel().getColor().equals(markupAnnotation.getColor())) { - annotationColumnPanel.removeAnnotation(markupAnnotation); - refreshPanelLayout(); - break; - } - } - } else { - // just add the component to the single column - ColorLabelPanel annotationColumnPanel = annotationNamedColorPanels.get(0); - annotationColumnPanel.removeAnnotation(markupAnnotation); - refreshPanelLayout(); - break; - } - } - } - break; - case PropertyConstants.ANNOTATION_UPDATED: - if (newValue instanceof PopupAnnotationComponent) { - // find an remove the markup annotation node. - if (annotationNamedColorPanels != null) { - PopupAnnotationComponent comp = (PopupAnnotationComponent) newValue; - MarkupAnnotation markupAnnotation = comp.getAnnotation().getParent(); - if (markupAnnotation != null) { - ArrayList colorLabels = DragDropColorList.retrieveColorLabels(); - if (colorLabels != null) { - for (ColorLabelPanel annotationColumnPanel : annotationNamedColorPanels) { - if (annotationColumnPanel.getColorLabel() != null && - annotationColumnPanel.getColorLabel().getColor().equals(markupAnnotation.getColor())) { - annotationColumnPanel.updateAnnotation(markupAnnotation); - break; - } - } - } else { - // just add the component to the single column - ColorLabelPanel annotationColumnPanel = annotationNamedColorPanels.get(0); - annotationColumnPanel.updateAnnotation(markupAnnotation); - break; - } - } - } - } - break; - case PropertyConstants.ANNOTATION_ADDED: - // rebuild the tree so we get a good sort etc and do worker thread setup. - if (newValue instanceof PopupAnnotationComponent) { - // find an remove the markup annotation node. - if (annotationNamedColorPanels != null) { - PopupAnnotationComponent comp = (PopupAnnotationComponent) newValue; - MarkupAnnotation markupAnnotation = comp.getAnnotation().getParent(); - if (markupAnnotation != null) { - ArrayList colorLabels = DragDropColorList.retrieveColorLabels(); - if (colorLabels != null) { - for (ColorLabelPanel annotationColumnPanel : annotationNamedColorPanels) { - if (annotationColumnPanel.getColorLabel() != null && - annotationColumnPanel.getColorLabel().getColor().equals(markupAnnotation.getColor())) { - annotationColumnPanel.addAnnotation(markupAnnotation); - refreshPanelLayout(); - break; - } - } - } else { - ColorLabelPanel annotationColumnPanel = annotationNamedColorPanels.get(0); - annotationColumnPanel.addAnnotation(markupAnnotation); - refreshPanelLayout(); - } - } - } - } - break; - case PropertyConstants.ANNOTATION_QUICK_COLOR_CHANGE: - if (lastSelectedMarkupAnnotation != null) { - // find and remove, - if (annotationNamedColorPanels != null) { - for (ColorLabelPanel annotationColumnPanel : annotationNamedColorPanels) { - annotationColumnPanel.removeAnnotation(lastSelectedMarkupAnnotation); - refreshPanelLayout(); - } - // and then add back in. - ArrayList colorLabels = DragDropColorList.retrieveColorLabels(); - if (colorLabels != null) { - for (ColorLabelPanel annotationColumnPanel : annotationNamedColorPanels) { - if (annotationColumnPanel.getColorLabel().getColor().equals(lastSelectedMarkupAnnotation.getColor())) { - annotationColumnPanel.addAnnotation(lastSelectedMarkupAnnotation); - refreshPanelLayout(); - break; - } - } - } else { - ColorLabelPanel annotationColumnPanel = annotationNamedColorPanels.get(0); - annotationColumnPanel.addAnnotation(lastSelectedMarkupAnnotation); - refreshPanelLayout(); - } - } - - } - break; - case PropertyConstants.ANNOTATION_COLOR_PROPERTY_PANEL_CHANGE: - // no choice but to do a full refresh, order will be lost. - refreshDocumentInstance(); - break; - case PropertyConstants.ANNOTATION_SELECTED: - case PropertyConstants.ANNOTATION_FOCUS_GAINED: - if (newValue instanceof MarkupAnnotationComponent) { - lastSelectedMarkupAnnotation = ((MarkupAnnotationComponent) newValue).getAnnotation(); - } - break; - - } - } - - private void applySelectedValue(JComboBox comboBox, Object value) { - comboBox.removeItemListener(this); - ValueLabelItem currentItem; - for (int i = 0; i < comboBox.getItemCount(); i++) { - currentItem = (ValueLabelItem) comboBox.getItemAt(i); - if (currentItem.getValue().equals(value)) { - comboBox.setSelectedIndex(i); - break; - } - } - comboBox.addItemListener(this); - } - - @Override - public void disposeDocument() { - annotationNamedColorPanels.clear(); - } - - private void addGB(JPanel layout, Component component, - int x, int y, - int rowSpan, int colSpan) { - constraints.gridx = x; - constraints.gridy = y; - constraints.gridwidth = rowSpan; - constraints.gridheight = colSpan; - layout.add(component, constraints); - } - - @Override - public void mouseClicked(MouseEvent e) { - if (e.getClickCount() == 2) { - Component comp = (Component) e.getSource(); - if (annotationNamedColorPanels != null) { - double weightX = 1.0 / (float) annotationNamedColorPanels.size(); - GridBagLayout gridBagLayout = (GridBagLayout) annotationsPanel.getLayout(); - for (ColorLabelPanel colorLabelPanel : annotationNamedColorPanels) { - GridBagConstraints constraints = gridBagLayout.getConstraints(colorLabelPanel); - if (colorLabelPanel.equals(comp)) { - constraints.weightx = 0.9; - } else { - constraints.weightx = weightX; - } - gridBagLayout.setConstraints(colorLabelPanel, constraints); - colorLabelPanel.invalidate(); - } - revalidate(); - } - } - } - - @Override - public void mousePressed(MouseEvent e) { - } - - @Override - public void componentResized(ComponentEvent e) { - // reset the constraint back to an even division of - if (annotationNamedColorPanels != null) { - double weightX = 1.0 / (float) annotationNamedColorPanels.size(); - GridBagLayout gridBagLayout = (GridBagLayout) annotationsPanel.getLayout(); - for (ColorLabelPanel colorLabelPanel : annotationNamedColorPanels) { - GridBagConstraints constraints = gridBagLayout.getConstraints(colorLabelPanel); - constraints.weightx = weightX; - gridBagLayout.setConstraints(colorLabelPanel, constraints); - } - invalidate(); - revalidate(); - } - } - - @Override - public void componentMoved(ComponentEvent e) { - - } - - @Override - public void componentShown(ComponentEvent e) { - - } - - @Override - public void componentHidden(ComponentEvent e) { - - } - - @Override - public void mouseReleased(MouseEvent e) { - - } - - @Override - public void mouseEntered(MouseEvent e) { - } - - @Override - public void mouseExited(MouseEvent e) { - } -} diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/ColorLabelPanel.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/ColorLabelPanel.java deleted file mode 100644 index 65cd40c3e..000000000 --- a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/ColorLabelPanel.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright 2006-2019 ICEsoft Technologies Canada Corp. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the - * License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an "AS - * IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language - * governing permissions and limitations under the License. - */ -package org.icepdf.ri.common.views.annotations.summary; - -import org.icepdf.core.pobjects.annotations.MarkupAnnotation; -import org.icepdf.core.pobjects.annotations.PopupAnnotation; -import org.icepdf.core.util.PropertyConstants; -import org.icepdf.ri.common.views.AbstractPageViewComponent; -import org.icepdf.ri.common.views.Controller; -import org.icepdf.ri.common.widgets.DragDropColorList; - -import javax.swing.*; -import java.awt.*; -import java.beans.PropertyChangeEvent; -import java.beans.PropertyChangeListener; -import java.util.List; - -/** - * - */ -public class ColorLabelPanel extends JPanel implements PropertyChangeListener { - - private final Controller controller; - private final DragDropColorList.ColorLabel colorLabel; - private final DraggableAnnotationPanel draggableAnnotationPanel; - - public ColorLabelPanel(Frame frame, Controller controller, DragDropColorList.ColorLabel colorLabel) { - super(); - this.colorLabel = colorLabel; - this.controller = controller; - - // setup the gui - setLayout(new BorderLayout()); - if (colorLabel != null) { - add(new JLabel("

" + colorLabel.getLabel() + "

", JLabel.CENTER), BorderLayout.NORTH); - } - draggableAnnotationPanel = new DraggableAnnotationPanel(frame); - add(new JScrollPane(draggableAnnotationPanel), BorderLayout.CENTER); - } - - public int getNumberOfComponents() { - return draggableAnnotationPanel.getComponentCount(); - } - - public void addAnnotation(MarkupAnnotation markupAnnotation) { - PopupAnnotation popupAnnotation = markupAnnotation.getPopupAnnotation(); - if (popupAnnotation != null) { - List pageComponents = - controller.getDocumentViewController().getDocumentViewModel().getPageComponents(); - int pageIndex = markupAnnotation.getPageIndex(); - if (pageIndex >= 0) { - AnnotationSummaryBox popupAnnotationComponent = - new AnnotationSummaryBox(popupAnnotation, - controller.getDocumentViewController(), pageComponents.get(pageIndex)); - popupAnnotationComponent.setVisible(true); - popupAnnotationComponent.removeMouseListeners(); - draggableAnnotationPanel.add(popupAnnotationComponent); - } - } - } - - public void updateAnnotation(MarkupAnnotation markupAnnotation) { - for (Component component : draggableAnnotationPanel.getComponents()) { - if (component instanceof AnnotationSummaryBox) { - AnnotationSummaryBox annotationSummaryBox = (AnnotationSummaryBox) component; - MarkupAnnotation currentMarkupAnnotation = annotationSummaryBox.getAnnotation().getParent(); - if (markupAnnotation.getPObjectReference().equals(currentMarkupAnnotation.getPObjectReference())) { - annotationSummaryBox.refreshPopupState(); - annotationSummaryBox.repaint(); - break; - } - } - } - } - - public void removeAnnotation(MarkupAnnotation markupAnnotation) { - for (Component component : draggableAnnotationPanel.getComponents()) { - if (component instanceof AnnotationSummaryBox) { - AnnotationSummaryBox annotationSummaryBox = (AnnotationSummaryBox) component; - MarkupAnnotation currentMarkupAnnotation = annotationSummaryBox.getAnnotation().getParent(); - if (markupAnnotation.getPObjectReference().equals(currentMarkupAnnotation.getPObjectReference())) { - draggableAnnotationPanel.remove(component); - draggableAnnotationPanel.revalidate(); - draggableAnnotationPanel.repaint(); - break; - } - } - } - } - - @Override - public void propertyChange(PropertyChangeEvent evt) { - Object newValue = evt.getNewValue(); - Object oldValue = evt.getOldValue(); - String propertyName = evt.getPropertyName(); - if (propertyName.equals(PropertyConstants.ANNOTATION_SUMMARY_BOX_FONT_SIZE_CHANGE)) { - Component[] comps = draggableAnnotationPanel.getComponents(); - Component comp; - for (Component component : comps) { - comp = component; - if (comp instanceof AnnotationSummaryBox) { - ((AnnotationSummaryBox) comp).setFontSize((int) newValue); - } - } - } - } - - public DragDropColorList.ColorLabel getColorLabel() { - return colorLabel; - } -} diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/DraggableAnnotationPanel.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/DraggableAnnotationPanel.java deleted file mode 100644 index e7bebf1a8..000000000 --- a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/DraggableAnnotationPanel.java +++ /dev/null @@ -1,328 +0,0 @@ -/* - * Copyright 2006-2019 ICEsoft Technologies Canada Corp. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the - * License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an "AS - * IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language - * governing permissions and limitations under the License. - */ -package org.icepdf.ri.common.views.annotations.summary; - -import org.icepdf.core.pobjects.annotations.Annotation; -import org.icepdf.ri.common.views.PageComponentSelector; - -import javax.swing.*; -import java.awt.*; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Comparator; - -public class DraggableAnnotationPanel extends JPanel { - - private static final int DEFAULT_GAP = 8; - - private boolean firstLoad = true; - - private Component dragComponent; - - private boolean isDragging; - - protected Frame frame; - - public DraggableAnnotationPanel(Frame frame, int layout, int hGap, int vHap) { - setLayout(new ColumnLayoutManager(vHap)); - - MouseHandler mouseHandler = new MouseHandler(); - addMouseListener(mouseHandler); - addMouseMotionListener(mouseHandler); - } - - public DraggableAnnotationPanel(Frame frame) { - this(frame, FlowLayout.CENTER, DEFAULT_GAP, DEFAULT_GAP); - } - - public class MouseHandler extends MouseAdapter { - - private Point dragOffset; - - @Override - public void mouseClicked(MouseEvent e) { - if (e.getButton() == MouseEvent.BUTTON3) { - Component comp = getComponentAt(e.getPoint()); - if (comp instanceof AnnotationSummaryBox) { - AnnotationSummaryBox annotationSummaryBox = (AnnotationSummaryBox) comp; - JPopupMenu contextMenu = annotationSummaryBox.getContextMenu(frame, this); - contextMenu.show(e.getComponent(), e.getX(), e.getY()); - } - } - if (e.getButton() == MouseEvent.BUTTON1 && - e.getClickCount() == 2) { - Component comp = getComponentAt(e.getPoint()); - if (comp instanceof AnnotationSummaryBox) { - AnnotationSummaryBox annotationSummaryBox = (AnnotationSummaryBox) comp; - Annotation annotation = annotationSummaryBox.getAnnotation().getParent(); - PageComponentSelector.SelectAnnotationComponent(annotationSummaryBox.getController(), annotation); - } - } - } - - @Override - public void mousePressed(MouseEvent e) { - Component comp = getComponentAt(e.getPoint()); - if (e.getButton() == MouseEvent.BUTTON1 && comp instanceof AnnotationSummaryBox) { - dragComponent = comp; - dragComponent.requestFocus(); - // bring the component to the front. - setComponentZOrder(dragComponent, 0); - // move to comp - dragOffset = new Point(); - dragOffset.x = e.getPoint().x - comp.getX(); - dragOffset.y = e.getPoint().y - comp.getY(); - repaint(); - } - } - - @Override - public void mouseReleased(MouseEvent e) { - if (isDragging) moveComponent(dragComponent); - isDragging = false; - dragComponent = null; - } - - public void checkForOverlap(Component dragComponent) { - int padding = 10; - Component[] comps = getComponents(); - Arrays.sort(comps, new ComponentBoundsCompare()); - Component comp, comp2; - // adjust for any overlap - for (int i = 0; i < comps.length - 1; i++) { - comp = comps[i]; - comp2 = comps[i + 1]; - Rectangle nextBounds = comp2.getBounds(); - if (comp.getBounds().intersects(nextBounds)) { - // over top but just below the top so we shift it back down. - comp2.setLocation(nextBounds.x, comp.getY() + comp.getHeight() + padding); - checkForOverlap(comp2); - } - } - } - - public void moveComponent(Component dragComponent) { - if (dragComponent != null) { - int padding = 10; - Component[] comps = getComponents(); - Arrays.sort(comps, new ComponentBoundsCompare()); - int draggedIndex = findComponentAt(comps, dragComponent); - Rectangle dragBounds = dragComponent.getBounds(); - Component comp; - // adjust for any overlap - for (int i = 0; i < comps.length; i++) { - comp = comps[i]; - if (i == draggedIndex) { - continue; - } - if (comp.getBounds().intersects(dragBounds)) { - // over top but just below the top so we shift it back down. - if (comp.getY() < dragBounds.y) { - dragComponent.setLocation(dragBounds.x, comp.getY() + comp.getHeight() + padding); - moveComponent(dragComponent); - } else { - comp.setLocation(dragBounds.x, dragComponent.getY() + dragComponent.getHeight() + padding); - moveComponent(comp); - } - } - } - - if (draggedIndex == 0) { - // make sure the component y > padding - if (dragComponent.getY() < padding) { - dragComponent.setLocation(dragComponent.getX(), padding); - moveComponent(dragComponent); - } - } - if (draggedIndex >= 1) { - comp = comps[draggedIndex - 1]; - int offset = dragComponent.getY() - (comp.getY() + comp.getHeight()); - if (offset < padding) { - dragComponent.setLocation(dragComponent.getX(), dragComponent.getY() + (padding - offset)); - moveComponent(dragComponent); - } - } - - revalidate(); - } - } - - - private int findComponentAt(Component[] comps, Component dragComponent) { - for (int i = 0; i < comps.length; i++) { - if (comps[i].equals(dragComponent)) { - return i; - } - } - return -1; - } - - - @Override - public void mouseDragged(MouseEvent e) { - isDragging = true; - if (dragComponent != null) { - firstLoad = false; - Point dragPoint = new Point(); - dragPoint.x = e.getPoint().x - dragOffset.x; - dragPoint.y = e.getPoint().y - dragOffset.y; - dragComponent.setLocation(dragPoint); - revalidate(); - repaint(); - } - } - - private int getComponentIndex(Component component) { - for (int i = 0; i < getComponentCount(); i++) { - if (component.equals(getComponent(i))) { - return i; - } - } - return -1; - } - } - - public class ColumnLayoutManager implements LayoutManager2 { - - public int padding = 10; - - private final ArrayList children; - - public ColumnLayoutManager(int padding) { - this(); - this.padding = padding; - } - - public ColumnLayoutManager() { - children = new ArrayList<>(25); - } - - @Override - public void addLayoutComponent(Component comp, Object constraints) { - children.add(comp); - } - - @Override - public Dimension maximumLayoutSize(Container target) { - int height = padding; - int width = 0; - int previousHeight = 0; - int paddingTwo = padding * 2; - // find last/tallest width - for (Component comp : children) { - width = target.getWidth() - paddingTwo; - height = Math.max(previousHeight, comp.getLocation().y + comp.getPreferredSize().height + padding); - previousHeight = height; - } - height += padding; - width += padding; - return new Dimension(width, height); - } - - @Override - public Dimension preferredLayoutSize(Container parent) { - return maximumLayoutSize(parent); - } - - @Override - public Dimension minimumLayoutSize(Container parent) { - return maximumLayoutSize(parent); - } - - @Override - public float getLayoutAlignmentX(Container target) { - return 0.5f; - } - - @Override - public float getLayoutAlignmentY(Container target) { - return 0.5f; - } - - @Override - public void invalidateLayout(Container target) { - } - - @Override - public void addLayoutComponent(String name, Component comp) { - } - - @Override - public void removeLayoutComponent(Component comp) { - children.remove(comp); - } - - @Override - public void layoutContainer(Container parent) { - if (firstLoad) { - evenLayout(parent); - } else { - stickyLayout(parent); - } - } - - private void stickyLayout(Container parent) { - Rectangle previousComp = new Rectangle(); - Rectangle currentComp; - int doublePadding = padding * 2; - Dimension preferredSize; - // sort the annotation by y coordinates. - Component[] comps = parent.getComponents(); - for (Component comp : comps) { - currentComp = comp.getBounds(); - preferredSize = comp.getPreferredSize(); - int x = padding; - int y = currentComp.y; - // size width - if (preferredSize.width > parent.getWidth()) { - comp.setBounds(x, y, comp.getWidth(), comp.getHeight()); - } else { - // stretch to fill - comp.setBounds(x, y, parent.getWidth() - doublePadding, preferredSize.height); - } - previousComp.setRect(currentComp); - } - - } - - private void evenLayout(Container parent) { - Point previousPoint = new Point(); - int doublePadding = padding * 2; - Dimension preferredSize; - for (Component comp : parent.getComponents()) { - int x = previousPoint.x + padding; - int y = previousPoint.y + padding; - preferredSize = comp.getPreferredSize(); - if (preferredSize.width > parent.getWidth()) { - comp.setBounds(x, y, comp.getWidth(), comp.getHeight()); - } else { - // stretch to fill - comp.setBounds(x, y, parent.getWidth() - doublePadding, preferredSize.height); - } - previousPoint.y = y + preferredSize.height; - } - } - } - - static class ComponentBoundsCompare implements Comparator { - @Override - public int compare(Component o1, Component o2) { - return Integer.compare(o1.getY(), o2.getY()); - } - } -} diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/MoveableComponentsPanel.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/MoveableComponentsPanel.java new file mode 100644 index 000000000..b8c4f0e28 --- /dev/null +++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/MoveableComponentsPanel.java @@ -0,0 +1,95 @@ +package org.icepdf.ri.common.views.annotations.summary; + +import javax.swing.*; +import java.awt.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +/** + * Represents a JPanel whose components can be moved up or down (swapping each other) + */ +public class MoveableComponentsPanel extends JPanel { + + /** + * Moves a component to a given index + * + * @param component The component to move + * @param idx The index to move the component to + */ + protected void move(final Component component, final int idx) { + if (Arrays.asList(getComponents()).contains(component)) { + remove(component); + add(component, idx); + } + revalidate(); + repaint(); + } + + /** + * Moves a component up + * + * @param c The component + */ + protected void moveUp(final Component c) { + final List components = Arrays.asList(getComponents()); + final int idx = components.indexOf(c); + if (idx > 0) { + move(c, idx - 1); + } + } + + /** + * Moves a component down + * + * @param c The component + */ + protected void moveDown(final Component c) { + final List components = Arrays.asList(getComponents()); + final int idx = components.indexOf(c); + if (idx != -1 && idx < components.size() - 1) { + move(c, idx + 1); + } + } + + /** + * Moves all the components given either up or down + * + * @param toMove The components to move + * @param up Up or down (true or false) + */ + public void moveAll(final Collection toMove, final boolean up) { + final List components = Arrays.asList(getComponents()); + if (components.containsAll(toMove)) { + final List sorted = getSortedList(toMove, up); + sorted.forEach(c -> { + if (up) { + moveUp(c); + } else { + moveDown(c); + } + }); + } + } + + /** + * Returns a sorted list for a given collection of components to move and a direction + * + * @param toSort The collection to sort + * @param ascending Whether the list must be in ascending or descending order + * @return The sorted list + */ + public List getSortedList(final Collection toSort, final boolean ascending) { + final List components = Arrays.asList(getComponents()); + if (components.containsAll(toSort)) { + final List sorted = new ArrayList<>(toSort); + sorted.sort((component, t1) -> { + final int idx1 = components.indexOf(component); + final int idx2 = components.indexOf(t1); + return ascending ? Integer.compare(idx1, idx2) : Integer.compare(idx2, idx1); + }); + return sorted; + } else return null; + } +} diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/SummaryPopupMenu.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/SummaryPopupMenu.java deleted file mode 100644 index 95f72ac3a..000000000 --- a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/SummaryPopupMenu.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright 2006-2019 ICEsoft Technologies Canada Corp. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the - * License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an "AS - * IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language - * governing permissions and limitations under the License. - */ -package org.icepdf.ri.common.views.annotations.summary; - -import org.icepdf.core.pobjects.annotations.MarkupAnnotation; -import org.icepdf.ri.common.views.Controller; -import org.icepdf.ri.common.views.annotations.AnnotationPopup; -import org.icepdf.ri.common.views.annotations.MarkupAnnotationComponent; - -import javax.swing.*; -import java.awt.*; -import java.awt.event.ActionEvent; -import java.awt.event.ItemEvent; -import java.awt.event.ItemListener; -import java.util.logging.Logger; - -/** - * The summary view is made up of annotation contents for markup annotations. The view however is built independently - * of the the page view and the component state may not be in correct state to use the default MarkupAnnotationPopupMenu - *

- * This class takes into account that the component state is not guaranteed. - * - * @since 6.3 - */ -public class SummaryPopupMenu extends AnnotationPopup implements ItemListener { - - private static final Logger logger = - Logger.getLogger(SummaryPopupMenu.class.toString()); - - protected final AnnotationSummaryBox annotationSummaryBox; - protected final MarkupAnnotation markupAnnotation; - protected final Frame frame; - protected JCheckBoxMenuItem showHideTextBlockMenuItem; - protected final DraggableAnnotationPanel.MouseHandler mouseHandler; - - public SummaryPopupMenu(AnnotationSummaryBox annotationSummaryBox, MarkupAnnotation markupAnnotation, MarkupAnnotationComponent annotationComponent, - Controller controller, Frame frame, DraggableAnnotationPanel.MouseHandler mouseHandler) { - super(annotationComponent, controller, null); - this.frame = frame; - this.markupAnnotation = markupAnnotation; - this.annotationSummaryBox = annotationSummaryBox; - this.mouseHandler = mouseHandler; - this.buildGui(); - } - - public void buildGui() { - showHideTextBlockMenuItem = new JCheckBoxMenuItem( - messageBundle.getString("viewer.annotation.popup.showHidTextBlock.label")); - showHideTextBlockMenuItem.setSelected(annotationSummaryBox.isShowTextBlockVisible()); - showHideTextBlockMenuItem.addItemListener(this); - add(showHideTextBlockMenuItem); - addSeparator(); - add(deleteMenuItem); - deleteMenuItem.addActionListener(this); - deleteMenuItem.setEnabled(controller.havePermissionToModifyDocument()); - addSeparator(); - add(propertiesMenuItem); - propertiesMenuItem.addActionListener(this); - } - - @Override - public void actionPerformed(ActionEvent e) { - Object source = e.getSource(); - if (source == null) return; - if (source == propertiesMenuItem) { - controller.showAnnotationProperties(annotationComponent, frame); - } else if (source == deleteMenuItem) { - controller.getDocumentViewController().deleteAnnotation(annotationComponent); - } - } - - @Override - public void itemStateChanged(ItemEvent e) { - if (e.getSource() == showHideTextBlockMenuItem) { - annotationSummaryBox.toggleTextBlockVisibility(); - annotationSummaryBox.invalidate(); - annotationSummaryBox.validate(); - SwingUtilities.invokeLater(() -> mouseHandler.checkForOverlap(annotationSummaryBox)); - } - } -} diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/colorpanel/ColorLabelPanel.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/colorpanel/ColorLabelPanel.java new file mode 100644 index 000000000..2f7b1f813 --- /dev/null +++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/colorpanel/ColorLabelPanel.java @@ -0,0 +1,181 @@ +/* + * Copyright 2006-2019 ICEsoft Technologies Canada Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an "AS + * IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ +package org.icepdf.ri.common.views.annotations.summary.colorpanel; + +import org.icepdf.core.pobjects.annotations.MarkupAnnotation; +import org.icepdf.core.pobjects.annotations.PopupAnnotation; +import org.icepdf.ri.common.views.AbstractPageViewComponent; +import org.icepdf.ri.common.views.DocumentViewController; +import org.icepdf.ri.common.views.annotations.summary.AnnotationSummaryBox; +import org.icepdf.ri.common.views.annotations.summary.AnnotationSummaryComponent; +import org.icepdf.ri.common.views.annotations.summary.AnnotationSummaryGroup; +import org.icepdf.ri.common.views.annotations.summary.mainpanel.SummaryController; +import org.icepdf.ri.common.widgets.DragDropColorList; + +import javax.swing.*; +import java.awt.*; +import java.util.List; +import java.util.UUID; +import java.util.function.Predicate; + +import static java.util.Objects.requireNonNull; + +/** + * + */ +public class ColorLabelPanel extends JPanel { + + private final DragDropColorList.ColorLabel colorLabel; + private final DraggableAnnotationPanel draggableAnnotationPanel; + protected final ColorPanelController controller; + protected final SummaryController summaryController; + + public ColorLabelPanel(final Frame frame, final DragDropColorList.ColorLabel colorLabel, final SummaryController summaryController) { + this.controller = createColorPanelController(); + this.colorLabel = requireNonNull(colorLabel); + this.summaryController = requireNonNull(summaryController); + + // setup the gui + setLayout(new BorderLayout()); + add(new JLabel("

" + colorLabel.getLabel() + "

", JLabel.CENTER), BorderLayout.NORTH); + draggableAnnotationPanel = createDraggableAnnotationPanel(frame); + draggableAnnotationPanel.addMouseWheelListener(e -> draggableAnnotationPanel.getParent() + .dispatchEvent(SwingUtilities.convertMouseEvent(draggableAnnotationPanel, e, draggableAnnotationPanel.getParent()))); + final JScrollPane scrollPane = new JScrollPane(draggableAnnotationPanel); + scrollPane.addMouseWheelListener(e -> dispatchEvent(SwingUtilities.convertMouseEvent(scrollPane, e, ColorLabelPanel.this))); + add(scrollPane, BorderLayout.CENTER); + addMouseWheelListener(e -> getParent().dispatchEvent(SwingUtilities.convertMouseEvent(this, e, getParent()))); + } + + protected ColorPanelController createColorPanelController() { + return new ColorPanelController(this); + } + + protected DraggableAnnotationPanel createDraggableAnnotationPanel(final Frame frame) { + return new DraggableAnnotationPanel(frame, this, summaryController); + } + + public ColorPanelController getController() { + return controller; + } + + public DraggableAnnotationPanel getDraggableAnnotationPanel() { + return draggableAnnotationPanel; + } + + public int getNumberOfComponents() { + return draggableAnnotationPanel.getComponentCount(); + } + + public void compact(final UUID uuid) { + draggableAnnotationPanel.compact(uuid); + } + + public AnnotationSummaryBox addAnnotation(final MarkupAnnotation markupAnnotation, final int y) { + final PopupAnnotation popupAnnotation = markupAnnotation.getPopupAnnotation(); + if (popupAnnotation == null) { + return null; + } else { + final List pageComponents = + summaryController.getController().getDocumentViewController().getDocumentViewModel().getPageComponents(); + final int pageIndex = markupAnnotation.getPageIndex(); + if (pageIndex >= 0) { + final AnnotationSummaryBox popupAnnotationComponent = createSummaryBox(popupAnnotation, + summaryController.getController().getDocumentViewController(), pageComponents.get(pageIndex), summaryController); + popupAnnotationComponent.setVisible(true); + popupAnnotationComponent.removeMouseListeners(); + final AnnotationSummaryGroup parent = summaryController.getGroupManager().getParentOf(markupAnnotation); + if (parent == null) { + draggableAnnotationPanel.add(popupAnnotationComponent, y); + draggableAnnotationPanel.revalidate(); + draggableAnnotationPanel.repaint(); + } else { + parent.addComponent(popupAnnotationComponent); + } + popupAnnotationComponent.fireComponentMoved(false, false, UUID.randomUUID()); + return popupAnnotationComponent; + } else return null; + } + } + + protected AnnotationSummaryBox createSummaryBox(final PopupAnnotation popupAnnotation, + final DocumentViewController documentViewController, + final AbstractPageViewComponent pvc, final SummaryController controller) { + return new AnnotationSummaryBox(popupAnnotation, documentViewController, pvc, controller); + } + + public AnnotationSummaryComponent findComponentFor(final Predicate filter) { + return draggableAnnotationPanel.findComponentFor(filter); + } + + public void addBox(final AnnotationSummaryBox box, final int y) { + draggableAnnotationPanel.add(box, y, box.isComponentSelected()); + draggableAnnotationPanel.revalidate(); + draggableAnnotationPanel.validate(); + draggableAnnotationPanel.repaint(); + } + + public void addComponent(final AnnotationSummaryComponent component, final int y) { + if (component instanceof AnnotationSummaryGroup) { + addGroup((AnnotationSummaryGroup) component, y); + } else if (component instanceof AnnotationSummaryBox) { + addBox((AnnotationSummaryBox) component, y); + } + } + + public void addComponent(final AnnotationSummaryComponent component) { + addComponent(component, -1); + } + + public AnnotationSummaryBox addAnnotation(final MarkupAnnotation markupAnnotation) { + if (markupAnnotation.isInReplyTo()) { + return null; + } else { + return addAnnotation(markupAnnotation, -1); + } + } + + public void addGroup(final AnnotationSummaryGroup group, final int y) { + draggableAnnotationPanel.add(group, y, group.isComponentSelected()); + draggableAnnotationPanel.revalidate(); + draggableAnnotationPanel.validate(); + draggableAnnotationPanel.repaint(); + } + + public void removeComponent(final AnnotationSummaryComponent c) { + draggableAnnotationPanel.remove(c.asComponent()); + } + + public void updateAnnotation(final MarkupAnnotation markupAnnotation) { + draggableAnnotationPanel.update(markupAnnotation); + } + + public void removeAnnotation(final MarkupAnnotation markupAnnotation) { + draggableAnnotationPanel.remove(markupAnnotation); + } + + public boolean contains(final MarkupAnnotation annotation) { + return draggableAnnotationPanel.contains(annotation); + } + + public DragDropColorList.ColorLabel getColorLabel() { + return colorLabel; + } + + public void updateFontFamily(final String familyName) { + controller.updateFontFamily(familyName); + } +} diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/colorpanel/ColorPanelController.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/colorpanel/ColorPanelController.java new file mode 100644 index 000000000..bb33a91b7 --- /dev/null +++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/colorpanel/ColorPanelController.java @@ -0,0 +1,46 @@ +package org.icepdf.ri.common.views.annotations.summary.colorpanel; + +import org.icepdf.core.util.PropertyConstants; +import org.icepdf.ri.common.views.annotations.summary.AnnotationSummaryComponent; + +import java.awt.*; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; + +public class ColorPanelController { + public final ColorLabelPanel panel; + public final PropertyChangeListener listener; + + public ColorPanelController(final ColorLabelPanel panel) { + this.panel = panel; + this.listener = new PropertyListener(); + panel.addPropertyChangeListener(PropertyConstants.ANNOTATION_SUMMARY_BOX_FONT_SIZE_CHANGE, listener); + } + + private class PropertyListener implements PropertyChangeListener { + @Override + public void propertyChange(final PropertyChangeEvent evt) { + final Object newValue = evt.getNewValue(); + final String propertyName = evt.getPropertyName(); + if (PropertyConstants.ANNOTATION_SUMMARY_BOX_FONT_SIZE_CHANGE.equals(propertyName)) { + final Component[] comps = panel.getDraggableAnnotationPanel().getComponents(); + for (final Component component : comps) { + if (component instanceof AnnotationSummaryComponent) { + ((AnnotationSummaryComponent) component).setFontSize((float) newValue); + } + } + } + + } + } + + public void updateFontFamily(final String familyName) { + if (familyName != null) { + for (final Component comp : panel.getDraggableAnnotationPanel().getComponents()) { + if (comp instanceof AnnotationSummaryComponent) { + ((AnnotationSummaryComponent) comp).setFontFamily(familyName); + } + } + } + } +} diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/colorpanel/DraggableAnnotationPanel.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/colorpanel/DraggableAnnotationPanel.java new file mode 100644 index 000000000..bcd26ebf9 --- /dev/null +++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/colorpanel/DraggableAnnotationPanel.java @@ -0,0 +1,411 @@ +/* + * Copyright 2006-2019 ICEsoft Technologies Canada Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an "AS + * IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ +package org.icepdf.ri.common.views.annotations.summary.colorpanel; + +import org.icepdf.core.pobjects.annotations.MarkupAnnotation; +import org.icepdf.ri.common.views.annotations.summary.AnnotationSummaryBox; +import org.icepdf.ri.common.views.annotations.summary.AnnotationSummaryComponent; +import org.icepdf.ri.common.views.annotations.summary.AnnotationSummaryGroup; +import org.icepdf.ri.common.views.annotations.summary.MoveableComponentsPanel; +import org.icepdf.ri.common.views.annotations.summary.mainpanel.SummaryController; + +import java.awt.*; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Comparator; +import java.util.List; +import java.util.UUID; +import java.util.function.Predicate; +import java.util.logging.Logger; + +public class DraggableAnnotationPanel extends MoveableComponentsPanel { + + private static final Logger logger = Logger.getLogger(DraggableAnnotationPanel.class.getName()); + private static final int DEFAULT_GAP = 8; + + + private Component dirtyC; + protected final DraggablePanelController panelController; + + public DraggableAnnotationPanel(final Frame frame, final ColorLabelPanel colorPanel, final SummaryController summaryController) { + this(frame, DEFAULT_GAP, summaryController); + } + + public DraggableAnnotationPanel(final Frame frame, final int vGap, final SummaryController summaryController) { + setLayout(new ColumnLayoutManager(vGap)); + this.panelController = createController(summaryController, frame); + } + + protected DraggablePanelController createController(final SummaryController summaryController, final Frame frame) { + return new DraggablePanelController(this, summaryController, frame); + } + + public Component add(final AnnotationSummaryComponent c, final int pos, final boolean selected) { + add(c.asComponent(), pos); + if (selected) { + panelController.addSelected(c.asComponent()); + } + return c.asComponent(); + } + + public boolean contains(final MarkupAnnotation annotation) { + return Arrays.stream(getComponents()) + .filter(AnnotationSummaryComponent.class::isInstance) + .flatMap(c -> ((AnnotationSummaryComponent) c).getAnnotations().stream()) + .anyMatch(a -> a.getPObjectReference().equals(annotation.getPObjectReference())); + } + + @Override + public Component add(final Component c, final int pos) { + if (pos >= getComponentCount()) { + panelController.setNeverDragged(false); + } + if (panelController.isNeverDragged()) { + if (pos == -1) { + super.add(c); + } else { + super.add(c, pos); + } + } else { + if (pos != -1) { + super.add(c); + c.setLocation(0, pos); + sortComponents(); + //Size is not known at this time, check for overlap during next layout + dirtyC = c; + } else if (getComponentCount() > 0) { + final Component lastComp = getComponent(getComponentCount() - 1); + super.add(c); + c.setLocation(0, lastComp.getY() + lastComp.getHeight() + 10); + } else { + super.add(c); + } + } + ((AnnotationSummaryComponent) c).fireComponentMoved(false, true, UUID.randomUUID()); + revalidate(); + repaint(); + return c; + } + + public int getFirstPosForComponents(final Collection components) { + final List sorted = getSortedList(components, true); + return sorted == null ? -1 : getPositionFor(sorted.get(0)); + } + + public int getPositionFor(final Component c) { + return panelController.isNeverDragged() ? Arrays.asList(getComponents()).indexOf(c) : c.getY(); + } + + protected void sortComponents() { + if (!panelController.isNeverDragged()) { + final Component[] sorted = getComponents(); + Arrays.sort(sorted, new ComponentBoundsCompare()); + removeAll(); + Arrays.stream(sorted).forEach(this::add); + } + } + + /** + * Removes a component given an annotation + * + * @param annot The annotation + */ + public void remove(final MarkupAnnotation annot) { + final Container c = findContainingComponent(annot); + final Component annotComp = findComponentForAnnotation(annot); + if (c != null && annotComp != null) { + panelController.getSummaryController().getGroupManager().removeFromGroup((AnnotationSummaryComponent) annotComp); + c.remove(annotComp); + c.revalidate(); + c.repaint(); + } + } + + /** + * Update a component given an annotation + * + * @param annot The annotation + */ + public void update(final MarkupAnnotation annot) { + final AnnotationSummaryBox box = (AnnotationSummaryBox) findComponentForAnnotation(annot); + if (box != null) { + box.refresh(); + //colorPanel.getSummaryPanel().refreshDocumentInstance(); + } + } + + private Container findContainingComponent(final MarkupAnnotation annot) { + return findContainingComponent(annot, this); + } + + private static Container findContainingComponent(final MarkupAnnotation annot, final Container parent) { + for (final Component c : parent.getComponents()) { + if (c instanceof AnnotationSummaryBox) { + if (checkSameAnnotations(annot, c)) { + return parent; + } + } else if (c instanceof AnnotationSummaryGroup) { + final Container result = findContainingComponent(annot, (Container) c); + if (result != null) { + return result; + } + } + } + return null; + } + + private Component findComponentForAnnotation(final MarkupAnnotation annot) { + return findComponentForAnnotation(annot, this); + } + + private static Component findComponentForAnnotation(final MarkupAnnotation annot, final Container parent) { + for (final Component c : parent.getComponents()) { + if (c instanceof AnnotationSummaryBox) { + if (checkSameAnnotations(annot, c)) { + return c; + } + } else if (c instanceof AnnotationSummaryGroup) { + final Component result = findComponentForAnnotation(annot, (Container) c); + if (result != null) { + return result; + } + } + } + return null; + } + + private static boolean checkSameAnnotations(final MarkupAnnotation annot, final Component c) { + if (c instanceof AnnotationSummaryBox) { + final MarkupAnnotation boxAnnot = ((AnnotationSummaryBox) c).getAnnotation().getParent(); + return boxAnnot.getPObjectReference().equals(annot.getPObjectReference()); + } else return false; + } + + + public void moveComponentToY(final Component c, final int y, final UUID uuid) { + moveComponentToY(c, y, true, uuid); + } + + public void moveComponentToY(final Component c, final int y, final boolean checkOverlap, final UUID uuid) { + panelController.setNeverDragged(false); + c.setLocation(c.getX(), y); + if (checkOverlap) { + checkForOverlap(uuid); + } + ((AnnotationSummaryComponent) c).fireComponentMoved(false, false, uuid); + validate(); + } + + public void checkForOverlap(final UUID uuid) { + final Component[] comps = getComponents(); + Arrays.sort(comps, new ComponentBoundsCompare()); + if (comps.length > 1) { + checkForOverlap(uuid, comps, 1); + } + } + + private static void checkForOverlap(final UUID uuid, final Component[] components, final int index) { + if (index < components.length) { + final Component refComp = components[index - 1]; + final Component curComp = components[index]; + final Rectangle bounds = curComp.getBounds(); + if (refComp.getBounds().intersects(bounds) || refComp.getY() + refComp.getHeight() + 10 > curComp.getY()) { + // over top but just below the top so we shift it back down. + curComp.setLocation(bounds.x, refComp.getY() + refComp.getHeight() + 10); + ((AnnotationSummaryComponent) curComp).fireComponentMoved(false, true, uuid); + } + checkForOverlap(uuid, components, index + 1); + } + } + + public void compact(final UUID uuid) { + final Component[] comps = getComponents(); + Arrays.sort(comps, new ComponentBoundsCompare()); + for (int i = 0; i < comps.length; ++i) { + final Component comp = comps[i]; + if (!panelController.getSummaryController().getDragAndLinkManager().isLeftLinked((AnnotationSummaryComponent) comp)) { + int y = -1; + for (int j = i - 1; j >= 0 && y == -1; --j) { + final Component prevComp = comps[j]; + if (!panelController.getSummaryController().getDragAndLinkManager().isLeftLinked((AnnotationSummaryComponent) prevComp)) { + final Component nextComp = comps[j + 1]; + if (nextComp == comp || nextComp.getY() - prevComp.getY() - prevComp.getHeight() - 20 >= comp.getHeight()) { + y = prevComp.getY() + prevComp.getHeight() + 10; + } + } + } + if (y == -1) { + for (int j = i - 1; j >= 0 && y == -1; --j) { + final Component prevComp = comps[j]; + final Component nextComp = comps[j + 1]; + if (nextComp == comp || nextComp.getY() - prevComp.getY() - prevComp.getHeight() - 20 >= comp.getHeight()) { + y = prevComp.getY() + prevComp.getHeight() + 10; + } + } + } + if (y == -1) { + y = 10; + } + comp.setLocation(comp.getX(), y); + ((AnnotationSummaryComponent) comp).fireComponentMoved(false, true, uuid); + } + } + validate(); + } + + public AnnotationSummaryComponent findComponentFor(final Predicate filter) { + return panelController.findComponentFor(filter); + } + + + public class ColumnLayoutManager implements LayoutManager2 { + + public int padding = 10; + + private final List children; + + public ColumnLayoutManager(final int padding) { + this(); + this.padding = padding; + } + + public ColumnLayoutManager() { + children = new ArrayList<>(25); + } + + @Override + public void addLayoutComponent(final Component comp, final Object constraints) { + children.add(comp); + } + + @Override + public Dimension maximumLayoutSize(final Container target) { + int height = padding; + int width = 0; + int previousHeight = 0; + final int paddingTwo = padding * 2; + // find last/tallest width + for (final Component comp : children) { + width = target.getWidth() - paddingTwo; + height = Math.max(previousHeight, comp.getLocation().y + comp.getPreferredSize().height + padding); + previousHeight = height; + } + height += padding; + width += padding; + return new Dimension(width, height); + } + + @Override + public Dimension preferredLayoutSize(final Container parent) { + return maximumLayoutSize(parent); + } + + @Override + public Dimension minimumLayoutSize(final Container parent) { + return maximumLayoutSize(parent); + } + + @Override + public float getLayoutAlignmentX(final Container target) { + return 0.5f; + } + + @Override + public float getLayoutAlignmentY(final Container target) { + return 0.5f; + } + + @Override + public void invalidateLayout(final Container target) { + } + + @Override + public void addLayoutComponent(final String name, final Component comp) { + addLayoutComponent(comp, null); + } + + @Override + public void removeLayoutComponent(final Component comp) { + children.remove(comp); + } + + @Override + public void layoutContainer(final Container parent) { + if (panelController.isNeverDragged()) { + evenLayout(parent); + } else { + stickyLayout(parent); + } + } + + private void stickyLayout(final Container parent) { + final Rectangle previousComp = new Rectangle(); + Rectangle currentComp; + final int doublePadding = padding * 2; + Dimension preferredSize; + // sort the annotation by y coordinates. + final Component[] comps = parent.getComponents(); + for (final Component comp : comps) { + currentComp = comp.getBounds(); + preferredSize = comp.getPreferredSize(); + final int x = padding; + final int y = currentComp.y; + // size width + if (preferredSize.width > parent.getWidth()) { + comp.setBounds(x, y, parent.getWidth() - doublePadding, comp.getHeight()); + } else { + // stretch to fill + comp.setBounds(x, y, parent.getWidth() - doublePadding, preferredSize.height); + } + previousComp.setRect(currentComp); + } + if (dirtyC != null) { + checkForOverlap(UUID.randomUUID()); + dirtyC = null; + } + } + + private void evenLayout(final Container parent) { + final Point previousPoint = new Point(); + final int doublePadding = padding * 2; + Dimension preferredSize; + for (final Component comp : parent.getComponents()) { + final int x = previousPoint.x + padding; + final int y = previousPoint.y + padding; + preferredSize = comp.getPreferredSize(); + if (preferredSize.width > parent.getWidth()) { + comp.setBounds(x, y, parent.getWidth() - doublePadding, comp.getHeight()); + } else { + // stretch to fill + comp.setBounds(x, y, parent.getWidth() - doublePadding, preferredSize.height); + } + previousPoint.y = y + preferredSize.height; + } + } + + } + + static class ComponentBoundsCompare implements Comparator, Serializable { + private static final long serialVersionUID = 133414022655106139L; + + @Override + public int compare(final Component o1, final Component o2) { + return Integer.compare(o1.getY(), o2.getY()); + } + } +} diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/colorpanel/DraggablePanelController.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/colorpanel/DraggablePanelController.java new file mode 100644 index 000000000..cbb4b3c88 --- /dev/null +++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/colorpanel/DraggablePanelController.java @@ -0,0 +1,491 @@ +package org.icepdf.ri.common.views.annotations.summary.colorpanel; + +import org.icepdf.core.pobjects.annotations.Annotation; +import org.icepdf.ri.common.views.PageComponentSelector; +import org.icepdf.ri.common.views.annotations.summary.AnnotationSummaryBox; +import org.icepdf.ri.common.views.annotations.summary.AnnotationSummaryComponent; +import org.icepdf.ri.common.views.annotations.summary.AnnotationSummaryGroup; +import org.icepdf.ri.common.views.annotations.summary.mainpanel.SummaryController; +import org.icepdf.ri.common.views.annotations.summary.menu.BoxMenuFactory; +import org.icepdf.ri.common.views.annotations.summary.menu.GroupMenuFactory; +import org.icepdf.ri.common.views.annotations.summary.menu.MultiSelectedPopupMenu; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.KeyEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; +import java.util.function.Predicate; +import java.util.logging.Logger; +import java.util.stream.Collectors; + +public class DraggablePanelController { + private Component dragComponent; + private boolean isDragging; + + protected final DraggableAnnotationPanel panel; + private final Set selectedComponents; + private final Frame frame; + + private boolean neverDragged = true; + + private final SummaryController summaryController; + + private static final Logger log = Logger.getLogger(DraggablePanelController.class.getName()); + + public DraggablePanelController(final DraggableAnnotationPanel panel, final SummaryController summaryController, final Frame frame) { + this.panel = panel; + this.selectedComponents = new HashSet<>(); + this.summaryController = summaryController; + this.frame = frame; + final MouseHandler handler = new MouseHandler(); + panel.addMouseListener(handler); + panel.addMouseMotionListener(handler); + final InputMap inputMap = panel.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); + final ActionMap actionMap = panel.getActionMap(); + + inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0), KeyEvent.VK_LEFT); + actionMap.put(KeyEvent.VK_LEFT, new BaseKeyAction() { + @Override + public void actionPerformed(final ActionEvent actionEvent) { + if (isValidState()) { + final Color newColor = summaryController.getLeftColor(panel); + if (newColor != null) { + final Set toRemove = new HashSet<>(); + for (final Component comp : selectedComponents) { + if (comp instanceof AnnotationSummaryComponent) { + if (!(comp instanceof AnnotationSummaryGroup && + summaryController.getGroupManager().groupExists(newColor, comp.getName()))) { + ((AnnotationSummaryComponent) comp).moveTo(newColor, true); + comp.requestFocusInWindow(); + toRemove.add(comp); + } + } + } + selectedComponents.removeAll(toRemove); + } + } + } + }); + + inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0), KeyEvent.VK_RIGHT); + actionMap.put(KeyEvent.VK_RIGHT, new BaseKeyAction() { + @Override + public void actionPerformed(final ActionEvent actionEvent) { + if (isValidState()) { + final Color newColor = summaryController.getRightColor(panel); + if (newColor != null) { + final Set toRemove = new HashSet<>(); + for (final Component comp : selectedComponents) { + if (!(comp instanceof AnnotationSummaryGroup && + summaryController.getGroupManager().groupExists(newColor, comp.getName()))) { + ((AnnotationSummaryComponent) comp).moveTo(newColor, true); + comp.requestFocusInWindow(); + toRemove.add(comp); + } + } + selectedComponents.removeAll(toRemove); + } + } + } + }); + + inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0), KeyEvent.VK_UP); + actionMap.put(KeyEvent.VK_UP, new BaseKeyAction() { + @Override + public void actionPerformed(final ActionEvent actionEvent) { + final Component focused = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner(); + if (isValidState()) { + final List parents = selectedComponents.stream().map(Component::getParent).collect(Collectors.toList()); + final Container parent = parents.get(0); + if (parents.stream().allMatch(p -> p == parent)) { + if (parent instanceof AnnotationSummaryGroup) { + ((AnnotationSummaryGroup) parent).moveAll(selectedComponents, true); + } else if (parent == panel && neverDragged) { + panel.moveAll(selectedComponents, true); + } + focused.requestFocusInWindow(); + } + } + } + }); + + inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0), KeyEvent.VK_DOWN); + actionMap.put(KeyEvent.VK_DOWN, new BaseKeyAction() { + @Override + public void actionPerformed(final ActionEvent actionEvent) { + final Component focused = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner(); + if (isValidState()) { + final List parents = selectedComponents.stream().map(Component::getParent).collect(Collectors.toList()); + final Container parent = parents.get(0); + if (parents.stream().allMatch(p -> p == parent)) { + if (parent instanceof AnnotationSummaryGroup) { + ((AnnotationSummaryGroup) parent).moveAll(selectedComponents, false); + } else if (parent == panel && neverDragged) { + panel.moveAll(selectedComponents, false); + } + } + focused.requestFocusInWindow(); + } + } + }); + + inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0), KeyEvent.VK_DELETE); + actionMap.put(KeyEvent.VK_DELETE, new BaseKeyAction() { + @Override + public void actionPerformed(final ActionEvent actionEvent) { + if (isValidState()) { + selectedComponents.forEach(c -> ((AnnotationSummaryComponent) c).delete()); + clearSelectedComponents(); + } + } + }); + } + + public abstract class BaseKeyAction extends AbstractAction { + + @Override + public abstract void actionPerformed(ActionEvent actionEvent); + + protected boolean isValidState() { + return !selectedComponents.isEmpty(); + } + + } + + void addSelected(final Component c) { + selectedComponents.add(c); + } + + void removeSelected(final Component c) { + selectedComponents.remove(c); + } + + public DraggableAnnotationPanel getPanel() { + return panel; + } + + public void setNeverDragged(final boolean neverDragged) { + this.neverDragged = neverDragged; + } + + public boolean isNeverDragged() { + return neverDragged; + } + + public SummaryController getSummaryController() { + return summaryController; + } + + public void clearSelectedComponents() { + selectedComponents.forEach(c -> { + ((AnnotationSummaryComponent) c).setComponentSelected(false); + summaryController.getDragAndLinkManager().removeComponentSelected((AnnotationSummaryComponent) c); + }); + selectedComponents.clear(); + } + + private void addSelectedComponent(final Component comp, final boolean toggle) { + final boolean select = !toggle || !selectedComponents.contains(comp); + if (select && getComponentIndex(comp) != -1) { + summaryController.getDragAndLinkManager().addComponentSelected((AnnotationSummaryComponent) comp); + } else { + summaryController.getDragAndLinkManager().removeComponentSelected((AnnotationSummaryComponent) comp); + } + if (comp instanceof AnnotationSummaryGroup) { + final Set intersection = ((AnnotationSummaryGroup) comp).getSubComponents().stream() + .map(AnnotationSummaryComponent::asComponent).collect(Collectors.toSet()); + intersection.retainAll(selectedComponents); + intersection.forEach(c -> ((AnnotationSummaryComponent) c).setComponentSelected(false)); + selectedComponents.removeAll(intersection); + if (select) { + selectedComponents.add(comp); + } else { + selectedComponents.remove(comp); + } + ((AnnotationSummaryComponent) comp).setComponentSelected(select); + } else if (comp instanceof AnnotationSummaryBox) { + if (!selectedComponents.contains(comp.getParent())) { + ((AnnotationSummaryComponent) comp).setComponentSelected(select); + if (select) { + selectedComponents.add(comp); + } else { + selectedComponents.remove(comp); + } + } + } + } + + private class MouseHandler extends MouseAdapter { + + private Point dragOffset; + private int lastSelectedComponentIdx = -1; + + @Override + public void mouseClicked(final MouseEvent e) { + final Component comp = panel.getComponentAt(e.getPoint()); + final Component deepestComponent = (Component) getDeepestComponentAt(panel, e.getPoint()); + comp.requestFocusInWindow(); + if (comp instanceof AnnotationSummaryComponent) { + if (SwingUtilities.isRightMouseButton(e)) { + //If there are selected components and we're clicking on one of them, show a multi-select menu + if (selectedComponents.size() > 1 && (selectedComponents.contains(comp) || selectedComponents.contains(deepestComponent))) { + final JPopupMenu menu = new MultiSelectedPopupMenu(comp, selectedComponents.stream() + .map(c -> (AnnotationSummaryComponent) c).collect(Collectors.toList()), + summaryController, DraggablePanelController.this); + menu.show(e.getComponent(), e.getX(), e.getY()); + } else { + //Otherwise just show a menu for the given component + JPopupMenu contextMenu = new JPopupMenu(); + clearSelectedComponents(); + final List allComponents = getAllComponentsAt(panel, e.getPoint(), new ArrayList<>()); + if (allComponents.size() > 1) { + for (final AnnotationSummaryComponent c : allComponents) { + if (c instanceof AnnotationSummaryGroup) { + final JMenu menu = GroupMenuFactory.createGroupMenu((AnnotationSummaryGroup) c, summaryController, + DraggablePanelController.this, ((AnnotationSummaryGroup) c).getName()); + contextMenu.add(menu); + } else if (c instanceof AnnotationSummaryBox) { + final JMenu menu = BoxMenuFactory.createBoxMenu((AnnotationSummaryBox) c, + ((AnnotationSummaryBox) c).getMarkupAnnotationComponent(), frame, + DraggablePanelController.this, summaryController, "Annotation"); + contextMenu.add(menu); + } + } + } else if (allComponents.size() == 1) { + if (comp instanceof AnnotationSummaryBox) { + contextMenu = BoxMenuFactory.createBoxPopupMenu((AnnotationSummaryBox) comp, + ((AnnotationSummaryBox) comp).getMarkupAnnotationComponent(), frame, + DraggablePanelController.this, summaryController); + } else if (comp instanceof AnnotationSummaryGroup) { + contextMenu = GroupMenuFactory.createGroupPopupMenu((AnnotationSummaryGroup) comp, + summaryController, DraggablePanelController.this); + } + } + contextMenu.show(e.getComponent(), e.getX(), e.getY()); + } + } else if (SwingUtilities.isLeftMouseButton(e)) { + //If no component is selected or CTRL is pressed, toggle the component (or the deepest if ALT is pressed) on the selected list + if (selectedComponents.isEmpty() || e.isControlDown()) { + if (e.isAltDown()) { + if (deepestComponent != null) { + deepestComponent.requestFocusInWindow(); + addSelectedComponent(deepestComponent, e.isControlDown()); + } + } else { + addSelectedComponent(comp, e.isControlDown()); + lastSelectedComponentIdx = getComponentIndex(comp); + } + //If a component is already selected and shif is down, select all the components in between + } else if (lastSelectedComponentIdx != -1 && e.isShiftDown()) { + final int newIdx = getComponentIndex(comp); + final int start = Math.min(lastSelectedComponentIdx, newIdx); + final int end = Math.max(lastSelectedComponentIdx, newIdx); + for (int i = start; i <= end; ++i) { + addSelectedComponent(panel.getComponent(i), false); + } + lastSelectedComponentIdx = getComponentIndex(comp); + //If simple click, clear the selected and add the clicked component to the selected list + } else if (!selectedComponents.contains(comp) || e.isAltDown() && !selectedComponents.contains(deepestComponent)) { + clearSelectedComponents(); + if (e.isAltDown()) { + if (deepestComponent != null) { + deepestComponent.requestFocusInWindow(); + addSelectedComponent(deepestComponent, false); + } + } else { + addSelectedComponent(comp, false); + lastSelectedComponentIdx = getComponentIndex(comp); + } + } + if (e.getClickCount() == 2) { + if (deepestComponent instanceof AnnotationSummaryBox) { + final Annotation annotation = ((AnnotationSummaryBox) deepestComponent).getAnnotation().getParent(); + PageComponentSelector.SelectAnnotationComponent(summaryController.getController(), annotation); + } + } + } + } else { + if (!e.isControlDown()) { + clearSelectedComponents(); + } + summaryController.dispatch(e); + } + } + + @Override + public void mousePressed(final MouseEvent e) { + final Component comp = panel.getComponentAt(e.getPoint()); + if (SwingUtilities.isLeftMouseButton(e) && comp instanceof AnnotationSummaryComponent) { + dragComponent = comp; + dragComponent.requestFocus(); + // bring the component to the front. + // setComponentZOrder(dragComponent, 0); + // move to comp + dragOffset = new Point(); + dragOffset.x = e.getPoint().x - comp.getX(); + dragOffset.y = e.getPoint().y - comp.getY(); + panel.repaint(); + } + } + + @Override + public void mouseReleased(final MouseEvent e) { + if (isDragging) moveComponent(dragComponent, !e.isControlDown()); + isDragging = false; + dragComponent = null; + } + + @Override + public void mouseDragged(final MouseEvent e) { + isDragging = true; + if (dragComponent != null) { + neverDragged = false; + panel.setComponentZOrder(dragComponent, 0); + final Point dragPoint = new Point(); + dragPoint.x = e.getPoint().x - dragOffset.x; + dragPoint.y = e.getPoint().y - dragOffset.y; + dragComponent.setLocation(dragPoint); + ((AnnotationSummaryComponent) dragComponent).fireComponentMoved(false, false, UUID.randomUUID()); + summaryController.setHasManuallyChanged(); + panel.validate(); + panel.repaint(); + } + } + + @Override + public void mouseMoved(final MouseEvent e) { + summaryController.dispatch(e); + } + } + + private void moveComponent(final Component draggedComponent, final boolean snap) { + if (draggedComponent != null) { + panel.sortComponents(); + innerMove(draggedComponent, snap); + } + } + + private void innerMove(final Component draggedComponent, final boolean snap) { + final int padding = 10; + final Component[] comps = panel.getComponents(); + final int draggedIndex = findComponentAt(comps, draggedComponent); + final Rectangle dragBounds = draggedComponent.getBounds(); + final Collection moved = new ArrayList<>(comps.length); + if (draggedIndex == 0 && dragBounds.getY() < padding) { + draggedComponent.setLocation(draggedComponent.getX(), padding); + } else if (draggedIndex > 0) { + final Component previousComponent = comps[draggedIndex - 1]; + if (previousComponent instanceof AnnotationSummaryGroup && previousComponent.getBounds().contains(dragBounds)) { + summaryController.getGroupManager().moveIntoGroup((AnnotationSummaryComponent) draggedComponent, + ((AnnotationSummaryGroup) previousComponent).getColor(), previousComponent.getName()); + } else if (previousComponent.getBounds().intersects(dragBounds)) { + draggedComponent.setLocation(dragBounds.x, previousComponent.getY() + previousComponent.getHeight() + padding); + moved.add((AnnotationSummaryComponent) draggedComponent); + } + } + + for (int i = draggedIndex + 1; i < comps.length; i++) { + final Component currentComponent = comps[i]; + for (int j = draggedIndex; j < i; ++j) { + final Component precedingComponent = comps[j]; + if (currentComponent.getBounds().intersects(precedingComponent.getBounds())) { + final Component previousComponent = comps[i - 1]; + currentComponent.setLocation(previousComponent.getX(), previousComponent.getY() + previousComponent.getHeight() + padding); + moved.add((AnnotationSummaryComponent) currentComponent); + } + } + } + + panel.revalidate(); + moved.forEach(c -> c.fireComponentMoved(snap, true, UUID.randomUUID())); + } + + private int findComponentAt(final Component[] comps, final Component dragComponent) { + for (int i = 0; i < comps.length; i++) { + if (comps[i].equals(dragComponent)) { + return i; + } + } + return -1; + } + + private int getComponentIndex(final Component component) { + for (int i = 0; i < panel.getComponentCount(); i++) { + if (component.equals(panel.getComponent(i))) { + return i; + } + } + return -1; + } + + /** + * Returns the deepest component at a given Point + * + * @param c The current component we're processing + * @param p The current point + * @return The deepest component + */ + private AnnotationSummaryComponent getDeepestComponentAt(final Component c, final Point p) { + final Component child = c.getComponentAt(p); + if (child instanceof AnnotationSummaryComponent) { + final Point newP = SwingUtilities.convertPoint(c, p, child); + if (child == c) { + return (AnnotationSummaryComponent) child; + } else { + final AnnotationSummaryComponent deepest = getDeepestComponentAt(child, newP); + return deepest == null ? (AnnotationSummaryComponent) child : deepest; + } + } else if (c instanceof AnnotationSummaryComponent) { + return (AnnotationSummaryComponent) c; + } else return null; + } + + /** + * Returns all the components at a given Point + * + * @param c The current component we're processing + * @param p The current point + * @param list The list of components + * @return A list of components + */ + private List getAllComponentsAt(final Component c, final Point p, + final List list) { + final Component child = c.getComponentAt(p); + if (child instanceof AnnotationSummaryComponent) { + //Sometimes StackOverflow for some reason + if (list.contains(child)) { + return list; + } else { + list.add((AnnotationSummaryComponent) child); + final Point newP = SwingUtilities.convertPoint(c, p, child); + return getAllComponentsAt(child, newP, list); + } + } else return list; + } + + public AnnotationSummaryComponent findComponentFor(final Predicate filter) { + final List components = Arrays.stream(panel.getComponents()) + .map(AnnotationSummaryComponent.class::cast).collect(Collectors.toList()); + final List found = components.stream().map(c -> { + if (c instanceof AnnotationSummaryBox) { + return filter.test((AnnotationSummaryBox) c) ? c : null; + } else if (c instanceof AnnotationSummaryGroup) { + return ((AnnotationSummaryGroup) c).findComponentFor(filter); + } else return null; + }).filter(Objects::nonNull).collect(Collectors.toList()); + if (found.size() > 1) { + log.warning("Multiple components found"); + } + return found.size() == 1 ? found.get(0) : null; + } +} diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/mainpanel/AnnotationSummaryPanel.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/mainpanel/AnnotationSummaryPanel.java new file mode 100644 index 000000000..84340872e --- /dev/null +++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/mainpanel/AnnotationSummaryPanel.java @@ -0,0 +1,569 @@ +/* + * Copyright 2006-2019 ICEsoft Technologies Canada Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an "AS + * IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ +package org.icepdf.ri.common.views.annotations.summary.mainpanel; + +import org.icepdf.core.util.Defs; +import org.icepdf.ri.common.utility.annotation.properties.FreeTextAnnotationPanel; +import org.icepdf.ri.common.utility.annotation.properties.ValueLabelItem; +import org.icepdf.ri.common.views.Controller; +import org.icepdf.ri.common.views.annotations.summary.AnnotationSummaryComponent; +import org.icepdf.ri.common.views.annotations.summary.colorpanel.ColorLabelPanel; +import org.icepdf.ri.common.widgets.DragDropColorList; +import org.icepdf.ri.util.Resources; +import org.icepdf.ri.util.ViewerPropertiesManager; + +import javax.swing.*; +import javax.swing.filechooser.FileFilter; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.InputEvent; +import java.awt.event.KeyEvent; +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.List; +import java.util.Timer; +import java.util.*; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Collectors; + +import static java.util.Objects.requireNonNull; + +public class AnnotationSummaryPanel extends JPanel { + private static final Logger LOG = Logger.getLogger(AnnotationSummaryPanel.class.getName()); + private static final boolean USE_JFILECHOOSER; + + static { + USE_JFILECHOOSER = Defs.booleanProperty("org.icepdf.ri.viewer.jfilechooser", false); + } + + private final ResourceBundle messageBundle; + private final SummaryController controller; + + protected final GridBagConstraints constraints; + protected JPanel annotationsPanel; + + // font configuration + private JComboBox fontNameBox; + private JComboBox fontSizeBox; + protected JPanel statusToolbarPanel; + + private static final int DEFAULT_FONT_SIZE_INDEX = 5; + + private static final List AVAILABLE_FONTS = Arrays.stream(GraphicsEnvironment.getLocalGraphicsEnvironment() + .getAvailableFontFamilyNames()).filter(f -> new Font(f, Font.PLAIN, 12).canDisplayUpTo("&èéàôö'%+<>ß") == -1) + .sorted(Comparator.naturalOrder()).map(s -> new ValueLabelItem(s, s)).collect(Collectors.toList()); + + + public AnnotationSummaryPanel(final Frame frame, final Controller controller) { + setLayout(new BorderLayout()); + setAlignmentY(JPanel.TOP_ALIGNMENT); + setFocusable(true); + constraints = new AbsoluteGridBagConstraints(); + this.messageBundle = controller.getMessageBundle(); + this.controller = createController(frame, controller); + + buildStatusToolBarPanel(); + + // add key listeners for ctr, 0, -, = : reset, decrease and increase font size. + addFontSizeBindings(); + addMouseWheelListener(e -> { + if (e.isControlDown()) { + fontSizeBox.setSelectedIndex(Math.max(0, Math.min(fontSizeBox.getSelectedIndex() - e.getWheelRotation(), + fontSizeBox.getItemCount() - 1))); + } + }); + } + + protected SummaryController createController(final Frame frame, final Controller controller) { + return new SummaryController(frame, controller, this); + } + + public SummaryController getController() { + return controller; + } + + JPanel getAnnotationsPanel() { + return annotationsPanel; + } + + private void addFontSizeBindings() { + final InputMap inputMap = getInputMap(WHEN_FOCUSED); + final ActionMap actionMap = getActionMap(); + + /// ctrl-- to increase font size. + KeyStroke key = KeyStroke.getKeyStroke(KeyEvent.VK_EQUALS, InputEvent.CTRL_MASK); + inputMap.put(key, "font-size-increase"); + actionMap.put("font-size-increase", new AbstractAction() { + @Override + public void actionPerformed(final ActionEvent e) { + if (fontSizeBox.getSelectedIndex() + 1 < fontSizeBox.getItemCount()) { + fontSizeBox.setSelectedIndex(fontSizeBox.getSelectedIndex() + 1); + } + } + }); + + // ctrl-0 to default font size. + key = KeyStroke.getKeyStroke(KeyEvent.VK_0, InputEvent.CTRL_MASK); + inputMap.put(key, "font-size-default"); + actionMap.put("font-size-default", new AbstractAction() { + @Override + public void actionPerformed(final ActionEvent e) { + fontSizeBox.setSelectedIndex(DEFAULT_FONT_SIZE_INDEX); + } + }); + + // ctrl-- to decrease font size. + key = KeyStroke.getKeyStroke(KeyEvent.VK_MINUS, InputEvent.CTRL_MASK); + inputMap.put(key, "font-size-decrease"); + actionMap.put("font-size-decrease", new AbstractAction() { + @Override + public void actionPerformed(final ActionEvent e) { + if (fontSizeBox.getSelectedIndex() - 1 >= 0) { + fontSizeBox.setSelectedIndex(fontSizeBox.getSelectedIndex() - 1); + } + } + }); + } + + public int getFontSize() { + return (int) ((ValueLabelItem) fontSizeBox.getSelectedItem()).getValue(); + } + + public JComboBox getFontNameBox() { + return fontNameBox; + } + + public JComboBox getFontSizeBox() { + return fontSizeBox; + } + + protected void buildStatusToolBarPanel() { + final ViewerPropertiesManager propertiesManager = controller.getController().getPropertiesManager(); + final ValueLabelItem[] sizes = FreeTextAnnotationPanel.generateFontSizeNameList(messageBundle); + final List sizeValues = Arrays.stream(sizes).map(vli -> (int) vli.getValue()).collect(Collectors.toList()); + final int labelSize = new JLabel().getFont().getSize(); + int idx = Collections.binarySearch(sizeValues, labelSize); + if (idx < 0) { + idx = Math.min(sizeValues.size() - 1, -(idx + 1)); + } + fontSizeBox = new JComboBox<>(sizes); + fontNameBox = new JComboBox<>(AVAILABLE_FONTS.toArray(new ValueLabelItem[0])); + final int size = propertiesManager.checkAndStoreIntProperty( + ViewerPropertiesManager.PROPERTY_ANNOTATION_SUMMARY_FONT_SIZE, sizeValues.get(idx)); + fontNameBox.setRenderer(new FontFamilyBoxRenderer(new JLabel().getFont().getSize())); + SummaryController.applySelectedValue(fontSizeBox, size); + SummaryController.applySelectedValue(fontNameBox, propertiesManager.checkAndStoreStringProperty( + ViewerPropertiesManager.PROPERTY_ANNOTATION_SUMMARY_FONT_NAME, new JLabel().getFont().getFamily())); + + statusToolbarPanel = new JPanel(new GridBagLayout()); + statusToolbarPanel.setAlignmentY(JPanel.TOP_ALIGNMENT); + constraints.fill = GridBagConstraints.NONE; + constraints.weightx = 0; + constraints.weighty = 1; + constraints.anchor = GridBagConstraints.WEST; + constraints.insets = new Insets(5, 5, 5, 5); + addGB(statusToolbarPanel, fontNameBox, 0, 0, 1, 1); + addGB(statusToolbarPanel, new JLabel(messageBundle.getString("viewer.annotationSummary.fontSize.label")), + 1, 0, 1, 1); + addGB(statusToolbarPanel, fontSizeBox, 2, 0, 1, 1); + constraints.anchor = GridBagConstraints.EAST; + final JButton headerButton = new JButton(messageBundle.getString("viewer.annotationSummary.header.button.hide")); + headerButton.addActionListener(new ActionListener() { + private boolean hidden = false; + + @Override + public void actionPerformed(final ActionEvent actionEvent) { + if (hidden) { + controller.showAllHeaders(); + } else { + controller.hideAllHeaders(); + } + hidden = !hidden; + final String basekey = "viewer.annotationSummary.header.button."; + final String key; + key = hidden ? basekey + "show" : basekey + "hide"; + headerButton.setText(messageBundle.getString(key)); + } + }); + addGB(statusToolbarPanel, headerButton, 4, 0, 1, 1); + final JButton compactButton = new JButton(messageBundle.getString("viewer.annotationSummary.compact.button")); + compactButton.addActionListener(actionEvent -> controller.compactPanel()); + addGB(statusToolbarPanel, compactButton, 5, 0, 1, 1); + final JButton saveButton = new JButton(messageBundle.getString("viewer.annotationSummary.save.button")); + saveButton.addActionListener(this::savePerformed); + final JButton exportButton = new JButton(messageBundle.getString("viewer.annotationSummary.export.button")); + exportButton.addActionListener(this::exportPerformed); + final JButton importButton = new JButton(messageBundle.getString("viewer.annotationSummary.import.button")); + importButton.addActionListener(this::importPerformed); + final List fileButtons = Arrays.asList(saveButton, exportButton, importButton); + fileButtons.forEach(b -> b.setVisible(controller.canSave())); + addGB(statusToolbarPanel, saveButton, 6, 0, 1, 1); + addGB(statusToolbarPanel, exportButton, 7, 0, 1, 1); + addGB(statusToolbarPanel, importButton, 8, 0, 1, 1); + constraints.weightx = 1; + addGB(statusToolbarPanel, new JLabel(), 3, 0, 1, 1); + controller.addFontListeners(); + } + + + public void refreshPanelLayout() { + removeAll(); + + annotationsPanel = new JPanel(new AbsoluteGridBagLayout()); + annotationsPanel.setAlignmentY(JPanel.TOP_ALIGNMENT); + controller.addPotentialLinkMouseListener(); + add(annotationsPanel, BorderLayout.CENTER); + add(statusToolbarPanel, BorderLayout.SOUTH); + + final ArrayList colorLabels = DragDropColorList.retrieveColorLabels(); + final int numberOfPanels = colorLabels.isEmpty() ? 1 : colorLabels.size(); + constraints.weightx = 1.0 / (float) numberOfPanels; + constraints.weighty = 1.0f; + constraints.insets = new Insets(0, 5, 0, 0); + constraints.fill = GridBagConstraints.BOTH; + int k = 0; + for (final ColorLabelPanel annotationColumnPanel : controller.getAnnotationNamedColorPanels()) { + if (annotationColumnPanel.getNumberOfComponents() > 0) { + addGB(annotationsPanel, annotationColumnPanel, ++k, 0, 1, 1); + } + } + revalidate(); + repaint(); + new Timer().schedule(new TimerTask() { + @Override + public void run() { + SwingUtilities.invokeLater(controller::refreshLinks); + controller.refreshCoordinates(); + } + }, 500); + annotationsPanel.addMouseWheelListener(e -> AnnotationSummaryPanel.this.dispatchEvent( + SwingUtilities.convertMouseEvent(annotationsPanel, e, AnnotationSummaryPanel.this))); + } + + public void refreshAnnotationPanel() { + annotationsPanel.revalidate(); + annotationsPanel.validate(); + annotationsPanel.repaint(); + } + + private void addGB(final JPanel layout, final Component component, + final int x, final int y, + final int rowSpan, final int colSpan) { + constraints.anchor = GridBagConstraints.CENTER; + constraints.gridx = x; + constraints.gridy = y; + constraints.gridwidth = rowSpan; + constraints.gridheight = colSpan; + layout.add(component, constraints); + } + + /** + * Adds to the layout using absolute coordinates + * + * @param layout The layout + * @param component The component to add + * @param x The x coordinate + * @param y The y coordinate + * @param width The width of the component + * @param height The height of the component + */ + void addAbsolute(final JPanel layout, final Component component, final int x, final int y, final int width, final int height) { + constraints.anchor = AbsoluteGridBagConstraints.ABSOLUTE; + component.setBounds(x, y, width, height); + layout.add(component, constraints); + + } + + /** + * Adds a LinkLabel given a component location + * + * @param component The component + * @param l The label to add + */ + public void addLabel(final AnnotationSummaryComponent component, final LinkLabel l) { + final Component c = component.asComponent(); + final int x = computeX(c, l); + final int y = computeY(c, l); + final Point point = SwingUtilities.convertPoint(c.getParent(), x, y, annotationsPanel); + final ImageIcon image = l.getImageIcon(); + addAbsolute(annotationsPanel, l, (int) point.getX(), (int) point.getY(), image.getIconWidth(), image.getIconHeight()); + } + + /** + * Updates a label + * + * @param c The component + * @param l The LinkLabel + */ + public void updateLabel(final Component c, final LinkLabel l) { + final int x = computeX(c, l); + final int y = computeY(c, l); + final Point point = SwingUtilities.convertPoint(c.getParent(), x, y, annotationsPanel); + l.setLocation((int) point.getX(), (int) point.getY()); + } + + /** + * Removes a label + * + * @param l The LinkLabel to remove + */ + public void removeLabel(final LinkLabel l) { + annotationsPanel.remove(l); + } + + private int computeX(final Component c, final LinkLabel l) { + final boolean left = l.isLeft(); + final boolean full = l.isFull(); + final ImageIcon image = l.getImageIcon(); + final int x = 8; + return left ? x - (full ? (image.getIconWidth() * 2 / 3) : image.getIconWidth() / 3) : x + c.getWidth() - + (full ? image.getIconWidth() / 4 : image.getIconWidth() * 2 / 3); + } + + private int computeY(final Component c, final LinkLabel l) { + final boolean bottom = l.isBottom(); + final ImageIcon image = l.getImageIcon(); + return bottom ? c.getY() + c.getHeight() - image.getIconHeight() : c.getY(); + } + + private void importPerformed(final ActionEvent actionEvent) { + final String extension = controller.getImportExportHandler().getFileExtension(); + if (!USE_JFILECHOOSER) { + final FileDialog chooser = new FileDialog(controller.getFrame()); + chooser.setMode(FileDialog.LOAD); + chooser.setMultipleMode(false); + final File defaultFile = new File(ViewerPropertiesManager.getInstance() + .checkAndStoreStringProperty(ViewerPropertiesManager.PROPERTY_ANNOTATION_SUMMARY_IMPORT_FILE, + System.getProperty("user.home") + File.separator + "export." + extension)); + final File defaultDir = defaultFile.getParentFile(); + if (defaultDir != null && defaultDir.exists()) { + chooser.setDirectory(defaultDir.getAbsolutePath()); + } + if (defaultFile.exists()) { + chooser.setFile(defaultFile.getAbsolutePath()); + } + chooser.setFilenameFilter((file, s) -> s.endsWith("." + extension)); + chooser.setVisible(true); + final String filename = chooser.getFile(); + final String dir = chooser.getDirectory(); + if (filename != null && dir != null) { + final String path = dir + filename; + importFile(path); + } + } else { + final JFileChooser chooser = new JFileChooser(); + chooser.setMultiSelectionEnabled(false); + final File defaultFile = new File(ViewerPropertiesManager.getInstance() + .checkAndStoreStringProperty(ViewerPropertiesManager.PROPERTY_ANNOTATION_SUMMARY_IMPORT_FILE, + System.getProperty("user.home") + File.separator + "export." + extension)); + final File defaultDir = defaultFile.getParentFile(); + if (defaultDir != null && defaultDir.exists()) { + chooser.setCurrentDirectory(defaultDir); + } + if (defaultFile.exists()) { + chooser.setSelectedFile(defaultFile); + } + chooser.setFileFilter(new ExtensionFileFilter(extension)); + final int ret = chooser.showSaveDialog(controller.getFrame()); + if (ret == JFileChooser.APPROVE_OPTION) { + final File file = chooser.getSelectedFile(); + final String path = file.getAbsolutePath(); + importFile(path); + } + } + } + + private void importFile(final String path) { + ViewerPropertiesManager.getInstance().set(ViewerPropertiesManager.PROPERTY_ANNOTATION_SUMMARY_IMPORT_FILE, + path); + try (final InputStream in = new BufferedInputStream(Files.newInputStream(Paths.get(path)))) { + controller.importFormat(in, true); + } catch (final FileNotFoundException ignored) { + + } catch (final IOException e) { + LOG.log(Level.SEVERE, "Error getting " + path, e); + } + } + + private void savePerformed(final ActionEvent actionEvent) { + controller.exportFormat(controller.getDefaultSummaryOutputStream()); + } + + private void exportPerformed(final ActionEvent actionEvent) { + final String extension = controller.getImportExportHandler().getFileExtension(); + if (!USE_JFILECHOOSER) { + final FileDialog chooser = new FileDialog(controller.getFrame()); + chooser.setMode(FileDialog.SAVE); + chooser.setMultipleMode(false); + final File defaultFile = new File(ViewerPropertiesManager.getInstance() + .checkAndStoreStringProperty(ViewerPropertiesManager.PROPERTY_ANNOTATION_SUMMARY_EXPORT_FILE, + System.getProperty("user.home") + File.separator + "export." + extension)); + final File defaultDir = defaultFile.getParentFile(); + if (defaultDir != null && defaultDir.exists()) { + chooser.setDirectory(defaultDir.getAbsolutePath()); + } + if (defaultFile.exists()) { + chooser.setFile(defaultFile.getAbsolutePath()); + } + chooser.setFilenameFilter((file, s) -> s.endsWith("." + extension)); + chooser.setVisible(true); + final String file = chooser.getFile(); + final String dir = chooser.getDirectory(); + if (file != null && dir != null) { + final String[] nameAndExt = file.split("\\."); + String name = file; + if (nameAndExt.length < 2) { + name = file + "." + extension; + } else if (!nameAndExt[nameAndExt.length - 1].equals(extension)) { + name = String.join(".", Arrays.copyOfRange(nameAndExt, 0, nameAndExt.length - 1)) + "." + extension; + } + final File parent = new File(dir + name).getParentFile(); + if (Files.isWritable(parent.toPath())) { + ViewerPropertiesManager.getInstance().set(ViewerPropertiesManager.PROPERTY_ANNOTATION_SUMMARY_EXPORT_FILE, + dir + name); + try { + controller.exportFormat(new FileOutputStream(dir + name)); + } catch (final FileNotFoundException ignored) { + + } + } else { + Resources.showMessageDialog( + controller.getFrame(), + JOptionPane.INFORMATION_MESSAGE, + messageBundle, + "viewer.dialog.saveAs.cantwrite.title", + "viewer.dialog.saveAs.cantwrite.msg", + new File(dir + name).getParentFile().getName()); + exportPerformed(actionEvent); + } + } + } else { + final JFileChooser chooser = new JFileChooser(); + chooser.setMultiSelectionEnabled(false); + final File defaultFile = new File(ViewerPropertiesManager.getInstance().checkAndStoreStringProperty( + ViewerPropertiesManager.PROPERTY_ANNOTATION_SUMMARY_EXPORT_FILE, System.getProperty("user.home") + + File.separator + "export." + extension)); + final File defaultDir = defaultFile.getParentFile(); + if (defaultDir != null && defaultDir.exists()) { + chooser.setCurrentDirectory(defaultDir); + } + if (defaultFile.exists()) { + chooser.setSelectedFile(defaultFile); + } + chooser.setFileFilter(new ExtensionFileFilter(extension)); + final int ret = chooser.showSaveDialog(controller.getFrame()); + if (ret == JFileChooser.APPROVE_OPTION) { + final File file = chooser.getSelectedFile(); + final File parent = file.getParentFile(); + if (Files.isWritable(parent.toPath())) { + ViewerPropertiesManager.getInstance().set(ViewerPropertiesManager.PROPERTY_ANNOTATION_SUMMARY_EXPORT_FILE, + file.getAbsolutePath()); + try { + controller.exportFormat(new FileOutputStream(file)); + } catch (final FileNotFoundException ignored) { + + } + } else { + Resources.showMessageDialog( + controller.getFrame(), + JOptionPane.INFORMATION_MESSAGE, + messageBundle, + "viewer.dialog.saveAs.cantwrite.title", + "viewer.dialog.saveAs.cantwrite.msg", + file.getParentFile().getName()); + exportPerformed(actionEvent); + } + } + } + } + + private static class AbsoluteGridBagLayout extends GridBagLayout { + + @Override + public void layoutContainer(final Container parent) { + final Map toRemove = new HashMap<>(); + for (final Component comp : parent.getComponents()) { + if (comptable.get(comp).anchor == AbsoluteGridBagConstraints.ABSOLUTE) { + toRemove.put(comp, comptable.get(comp)); + } + } + for (final Component comp : toRemove.keySet()) { + parent.remove(comp); + } + super.layoutContainer(parent); + for (final Map.Entry entry : toRemove.entrySet()) { + final Component comp = entry.getKey(); + parent.add(comp, entry.getValue()); + parent.setComponentZOrder(comp, 0); + comp.setBounds(comp.getBounds()); + } + } + } + + private static class AbsoluteGridBagConstraints extends GridBagConstraints { + public static final int ABSOLUTE = Integer.MAX_VALUE; + } + + private static class FontFamilyBoxRenderer extends JLabel implements ListCellRenderer { + + private final int fontSize; + + private FontFamilyBoxRenderer(final int size) { + this.fontSize = size; + } + + @Override + public Component getListCellRendererComponent(final JList jList, + final ValueLabelItem valueLabelItem, + final int i, final boolean b, final boolean b1) { + if (valueLabelItem != null) { + final String name = (String) valueLabelItem.getValue(); + setText(name); + setFont(new Font(name, Font.PLAIN, fontSize)); + } else { + setText(""); + } + return this; + } + } + + private static class ExtensionFileFilter extends FileFilter { + + private final String extension; + + private ExtensionFileFilter(final String extension) { + this.extension = requireNonNull(extension); + } + + @Override + public boolean accept(final File file) { + return Files.isDirectory(file.toPath()) || file.getName().endsWith("." + extension); + } + + @Override + public String getDescription() { + return "OpenDocument Spreadsheet (*.ods)"; + } + } +} diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/mainpanel/DragAndLinkManager.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/mainpanel/DragAndLinkManager.java new file mode 100644 index 000000000..d8bbae65e --- /dev/null +++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/mainpanel/DragAndLinkManager.java @@ -0,0 +1,559 @@ +package org.icepdf.ri.common.views.annotations.summary.mainpanel; + +import org.icepdf.ri.common.views.annotations.summary.AnnotationSummaryComponent; +import org.icepdf.ri.common.views.annotations.summary.AnnotationSummaryGroup; +import org.icepdf.ri.common.views.annotations.summary.colorpanel.ColorLabelPanel; +import org.icepdf.ri.common.widgets.DragDropColorList; + +import java.awt.*; +import java.util.List; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; + +/** + * Class managing the dragging and linking of the components + */ +public class DragAndLinkManager { + private final Map> potentialLinks; + private final Map> linkedComponents; + private final Map> colorToComps; + private final Map> selectedComponents; + private final Map> movedForUUID; + private final Map timeForUUID; + private static final long UUID_PURGE_TIME = 5000000000L; // 5sec + private static final long UUID_TIMER_SCHEDULE = 10000; //10sec + + private final SummaryController controller; + + public DragAndLinkManager(final SummaryController controller) { + this.controller = controller; + this.colorToComps = new HashMap<>(); + this.linkedComponents = new HashMap<>(); + this.potentialLinks = new HashMap<>(); + this.selectedComponents = new HashMap<>(); + this.movedForUUID = new ConcurrentHashMap<>(); + this.timeForUUID = new ConcurrentHashMap<>(); + final Timer clearTimer = new Timer(); + clearTimer.scheduleAtFixedRate(new TimerTask() { + @Override + public void run() { + final long now = System.nanoTime(); + final Set toDelete = new HashSet<>(); + timeForUUID.forEach((uuid, time) -> { + if (now - time > UUID_PURGE_TIME) { + toDelete.add(uuid); + } + }); + toDelete.forEach(uuid -> { + timeForUUID.remove(uuid); + movedForUUID.remove(uuid); + }); + } + }, UUID_TIMER_SCHEDULE, UUID_TIMER_SCHEDULE); + } + + /** + * Checks if a component is linked + * + * @param comp The component to check + * @return true if it is linked + */ + public boolean isLinked(final AnnotationSummaryComponent comp) { + final Set linked = linkedComponents.get(comp); + return linked != null && !linked.isEmpty(); + } + + /** + * Checks if a component is linked to a given color + * + * @param comp The component + * @param c The color + * @return true if they are linked + */ + public boolean areLinked(final AnnotationSummaryComponent comp, final Color c) { + final Set linked = linkedComponents.get(comp); + if (linked != null) { + for (final Color color : linked.stream().map(AnnotationSummaryComponent::getColor).collect(Collectors.toList())) { + if (color.equals(c)) { + return true; + } + } + } + return false; + } + + /** + * Checks if two components are linked + * + * @param comp1 The first component + * @param comp2 The second component + * @return true if they are linked + */ + public boolean areLinked(final AnnotationSummaryComponent comp1, final AnnotationSummaryComponent comp2) { + final Set linked = linkedComponents.get(comp1); + return linked != null && linked.contains(comp2); + } + + /** + * Clears the links + */ + public void clear() { + linkedComponents.clear(); + potentialLinks.clear(); + selectedComponents.values().forEach(s -> s.forEach(comp -> comp.setComponentSelected(false))); + selectedComponents.clear(); + } + + /** + * Checks if a component is linked to a component on its left + * + * @param comp The component + * @return true if the component is linked to its left + */ + public boolean isLeftLinked(final AnnotationSummaryComponent comp) { + final Set linked = linkedComponents.get(comp); + if (linked != null) { + for (final Color color : linked.stream().map(AnnotationSummaryComponent::getColor).collect(Collectors.toList())) { + if (controller.isLeftOf(color, comp.getColor())) { + return true; + } + } + } + return false; + } + + public void linkComponents(final Collection components) { + if (components.stream().map(AnnotationSummaryComponent::getColor).distinct().count() == components.size()) { + addAllRelationsTo(components, linkedComponents); + } + } + + /** + * Links two components + * + * @param c1 The first component + * @param c2 The second component + */ + public void linkWithNeighbor(final AnnotationSummaryComponent c1, final AnnotationSummaryComponent c2) { + if (!c1.getColor().equals(c2.getColor()) && c1.asComponent().getY() == c2.asComponent().getY()) { + linkComponents(c1, c2); + } + } + + /** + * Links a group of component with a component + * + * @param comps The components to create the group with + * @param comp The component + * @param idx The idx of the component in the group that will determine its position + */ + public void linkWithNeighbor(final Collection comps, final AnnotationSummaryComponent comp, final int idx) { + final AnnotationSummaryGroup group = createAndGetGroup(comps, idx); + linkWithNeighbor(group, comp); + } + + /** + * Links a component with the selected components in a column + * + * @param c The component + * @param color The color of the column + */ + public void linkWithSelected(final AnnotationSummaryComponent c, final Color color) { + final Set selected = selectedComponents.get(color); + final UUID uuid = UUID.randomUUID(); + if (selected != null) { + if (selected.size() > 1) { + final List selectedComponents = new ArrayList<>(selected); + AnnotationSummaryGroup selectedGroup = null; + for (int i = 0; i < selectedComponents.size() && selectedGroup == null; ++i) { + if (selectedComponents.get(i).asComponent().getY() == c.asComponent().getY()) { + selectedGroup = createAndGetGroup(selectedComponents, i); + } + } + if (selectedGroup == null) { + selectedGroup = createAndGetGroupWithY(selectedComponents, c.asComponent().getY()); + } + if (selectedGroup != null) { + linkComponents(c, selectedGroup); + controller.getColorPanelFor(selectedGroup).getDraggableAnnotationPanel().moveComponentToY( + selectedGroup.asComponent(), c.asComponent().getY(), uuid); + } + } else { + selected.forEach(comp -> { + linkComponents(c, comp); + controller.getColorPanelFor(comp).getDraggableAnnotationPanel().moveComponentToY( + comp.asComponent(), c.asComponent().getY(), uuid); + }); + } + } + } + + /** + * Links a group of component with the selected components in a column + * + * @param comps The components + * @param color The color of the column + * @param idx The idx that will determine the position of the group + */ + public void linkWithSelected(final Collection comps, final Color color, final int idx) { + final AnnotationSummaryGroup group = createAndGetGroup(comps, idx); + linkWithSelected(group, color); + } + + private AnnotationSummaryGroup createAndGetGroupWithY(final Collection comps, final int y) { + final List components = new ArrayList<>(comps); + final AnnotationSummaryComponent c = components.get(0); + final GroupManager gm = controller.getGroupManager(); + gm.createGroup(components, y); + final AnnotationSummaryGroup group = gm.getParentOf(c); + updateRelation(c, group, potentialLinks); + updateRelation(c, group, linkedComponents); + comps.forEach(comp -> { + if (comp != c) { + deleteFromMap(comp, linkedComponents); + deleteFromMap(comp, potentialLinks); + } + }); + return group; + } + + private AnnotationSummaryGroup createAndGetGroup(final Collection comps, final int idx) { + final List components = new ArrayList<>(comps); + final AnnotationSummaryComponent c = components.get(idx); + final GroupManager gm = controller.getGroupManager(); + gm.createGroup(components, c.asComponent().getY()); + final AnnotationSummaryGroup group = gm.getParentOf(c); + updateRelation(c, group, potentialLinks); + updateRelation(c, group, linkedComponents); + comps.forEach(comp -> { + if (comp != c) { + deleteFromMap(comp, linkedComponents); + deleteFromMap(comp, potentialLinks); + } + }); + return group; + } + + private void deleteFromMap(final T value, final Map> map) { + if (map.containsKey(value)) { + final Set values = map.remove(value); + values.forEach(v -> { + final Set vValues = map.get(v); + if (vValues != null) { + vValues.remove(value); + } + }); + } + } + + private void updateRelation(final T oldV, final T newV, final Map> map) { + if (map.containsKey(oldV)) { + final Set values = map.remove(oldV); + values.forEach(v -> { + final Set vValues = map.get(v); + vValues.remove(oldV); + vValues.add(newV); + }); + map.put(newV, values); + } + } + + /** + * Link two components + * + * @param c1 the first component + * @param c2 the second component + */ + public void linkComponents(final AnnotationSummaryComponent c1, final AnnotationSummaryComponent c2) { + if (!c1.getColor().equals(c2.getColor())) { + controller.setHasManuallyChanged(); + addRelation(c1, c2, linkedComponents); + removeRelation(c1, c2, potentialLinks); + controller.refreshLinks(); + } + } + + /** + * Unlink two components + * + * @param c1 The first component + * @param c2 The second component + */ + public void unlinkComponents(final AnnotationSummaryComponent c1, final AnnotationSummaryComponent c2) { + controller.setHasManuallyChanged(); + removeRelation(c1, c2, linkedComponents); + controller.refreshLinks(); + } + + private static void addRelation(final T t1, final T t2, final Map> map) { + if (map.containsKey(t1)) { + final Set rel1 = new HashSet<>(map.get(t1)); + if (map.containsKey(t2)) { + final Set rel2 = new HashSet<>(map.get(t2)); + rel1.add(t2); + rel2.add(t1); + map.put(t1, rel1); + map.put(t2, rel2); + } else { + final Set rel2 = new HashSet<>(); + rel1.add(t2); + rel2.add(t1); + map.put(t1, rel1); + map.put(t2, rel2); + } + } else { + if (map.containsKey(t2)) { + final Set rel2 = new HashSet<>(map.get(t2)); + final Set rel1 = new HashSet<>(); + rel1.add(t2); + rel2.add(t1); + map.put(t1, rel1); + map.put(t2, rel2); + } else { + final Set set1 = new HashSet<>(); + set1.add(t2); + final Set set2 = new HashSet<>(); + set2.add(t1); + map.put(t1, set1); + map.put(t2, set2); + } + } + } + + private static void removeRelation(final T t1, final T t2, final Map> map) { + if (map.containsKey(t1)) { + final Set rel1 = new HashSet<>(map.get(t1)); + if (map.containsKey(t2)) { + final Set rel2 = new HashSet<>(map.get(t2)); + rel1.remove(t2); + rel2.remove(t1); + map.put(t1, rel1); + map.put(t2, rel2); + } else { + final Set rel2 = new HashSet<>(); + rel1.remove(t2); + map.put(t1, rel1); + map.put(t2, rel2); + } + } else { + if (map.containsKey(t2)) { + final Set rel2 = new HashSet<>(map.get(t2)); + final Set rel1 = new HashSet<>(rel2); + rel1.remove(t2); + rel2.remove(t1); + map.put(t1, rel1); + map.put(t2, rel2); + } + } + } + + private static void addAllRelationsTo(final Collection toAdd, final Map> map) { + toAdd.forEach(t -> { + final Set rel = new HashSet<>(toAdd); + rel.remove(t); + map.put(t, rel); + }); + } + + /** + * @return The map of linked components + */ + public Map> getLinkedComponents() { + return linkedComponents; + } + + /** + * Unlink a component + * + * @param c The component to unlink + * @param keep If the component will still exists + */ + public void unlinkComponent(final AnnotationSummaryComponent c, final boolean keep) { + if (keep) { + transferValues(c, linkedComponents, potentialLinks); + } else { + deleteFromMap(c, linkedComponents); + } + controller.refreshLinks(); + } + + /** + * Unlink all components in the list + * + * @param components The components to unlink + * @param keep If the components will still exist + */ + public void unlinkAll(final Collection components, final boolean keep) { + components.forEach(c -> unlinkComponent(c, keep)); + } + + private void transferValues(final T key, final Map> origin, final Map> dest) { + final Set values = origin.get(key); + deleteFromMap(key, origin); + dest.put(key, values); + values.forEach(v -> { + final Set vValues = new HashSet<>(values); + vValues.remove(v); + vValues.add(key); + dest.put(v, vValues); + }); + } + + public void unlinkAllComponents(final AnnotationSummaryComponent c) { + if (linkedComponents.containsKey(c)) { + final Set components = linkedComponents.remove(c); + components.forEach(linkedComponents::remove); + } + } + + /** + * Notifies that a component has been moved + * + * @param c The component that moved + * @param snap If the component is to be snapped in position + * @param check If the column needs to be checked for overlap + * @param uuid The uuid of the operation (to avoid infinite loop of checking/snapping) + */ + public void componentMoved(final AnnotationSummaryComponent c, final boolean snap, final boolean check, final UUID uuid) { + if (!movedForUUID.containsKey(uuid) || movedForUUID.get(uuid) == null || !movedForUUID.get(uuid).contains(c)) { + if (!timeForUUID.containsKey(uuid)) { + timeForUUID.put(uuid, System.nanoTime()); + } + Set comps = movedForUUID.get(uuid); + if (comps == null) { + comps = new HashSet<>(); + comps.add(c); + movedForUUID.put(uuid, comps); + } else { + comps.add(c); + } + deleteFromMap(c, potentialLinks); + final Component cComp = (Component) c; + final int y = cComp.getY(); + Set colorComponents = colorToComps.get(c.getColor()); + if (colorComponents == null) { + colorComponents = new HashSet<>(); + } + colorComponents.add(cComp); + colorToComps.put(c.getColor(), colorComponents); + if (snap) { + final ColorLabelPanel panel = controller.getColorPanelFor(c); + if (panel != null) { + for (final Map.Entry> entry : colorToComps.entrySet()) { + if (!entry.getKey().equals(c.getColor())) { + for (final Component component : entry.getValue()) { + final int compY = component.getY(); + final int snapHeight = 30; + if (!linkedComponents.getOrDefault(c, new HashSet<>()).contains(component) + && y != compY && y > compY - snapHeight && y < compY + snapHeight) { + panel.getDraggableAnnotationPanel().moveComponentToY(cComp, compY, uuid); + addRelation(c, (AnnotationSummaryComponent) component, potentialLinks); + break; + } + } + } + } + } + } + if (linkedComponents.containsKey(c)) { + linkedComponents.get(c).forEach(comp -> { + final ColorLabelPanel panel = controller.getColorPanelFor(comp); + //Don't reuse y as it may have changed after snapping + if (panel != null && (cComp.getY() != ((Component) comp).getY() || check)) { + panel.getDraggableAnnotationPanel().moveComponentToY((Component) comp, cComp.getY(), check, uuid); + } + }); + } + controller.componentMoved(c); + } + } + + /** + * Indicates if a component is selected + * + * @param component The component + * @param selected The selection status + */ + public void setComponentSelected(final AnnotationSummaryComponent component, final boolean selected) { + final Color color = component.getColor(); + Set components = selectedComponents.get(color); + if (selectedComponents.get(color) == null) { + components = new HashSet<>(); + if (selected) { + components.add(component); + } + selectedComponents.put(color, components); + } else { + if (selected) { + components.add(component); + } else { + components.remove(component); + } + } + } + + /** + * Indicates that a component is selected + * + * @param c The component + */ + public void addComponentSelected(final AnnotationSummaryComponent c) { + setComponentSelected(c, true); + } + + /** + * Indicates that a component is unselected + * + * @param c The component + */ + public void removeComponentSelected(final AnnotationSummaryComponent c) { + setComponentSelected(c, false); + } + + /** + * Returns a map of color to component which indicates neighbor components which are at the same height + * + * @param component The component to check against + * @return The map of components + */ + public Map getSuitableNeighbors(final AnnotationSummaryComponent component) { + final Map neighbors = new HashMap<>(); + for (final ColorLabelPanel panel : controller.getAnnotationNamedColorPanels()) { + if (!panel.getColorLabel().getColor().equals(component.getColor())) { + for (final Component c : panel.getDraggableAnnotationPanel().getComponents()) { + if (c.getY() == component.asComponent().getY() && !areLinked(component, panel.getColorLabel().getColor())) { + neighbors.put(panel.getColorLabel(), (AnnotationSummaryComponent) c); + } + } + } + } + return neighbors; + } + + /** + * Returns a list of columns which have suitable selected components + * + * @param component The component to check against + * @return The list of colors + */ + public List getSuitableSelected(final AnnotationSummaryComponent component) { + final List components = new ArrayList<>(); + final Color color = component.getColor(); + for (final Map.Entry> entry : selectedComponents.entrySet()) { + final Color c = entry.getKey(); + if (!c.equals(color) && !areLinked(component, c)) { + final Set selected = entry.getValue(); + final GroupManager gm = controller.getGroupManager(); + if (selected != null && selected.stream().anyMatch(comp -> !areLinked(comp, color)) && + selected.stream().allMatch(comp -> + gm.getParentOf(comp) == gm.getParentOf(selected.iterator().next())) + && gm.getParentOf(selected.iterator().next()) == null) { + components.add(controller.getColorLabelFor(c)); + } + } + } + return components; + } + +} diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/mainpanel/GroupManager.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/mainpanel/GroupManager.java new file mode 100644 index 000000000..9d146f43b --- /dev/null +++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/mainpanel/GroupManager.java @@ -0,0 +1,340 @@ +package org.icepdf.ri.common.views.annotations.summary.mainpanel; + +import org.icepdf.core.pobjects.Reference; +import org.icepdf.core.pobjects.annotations.MarkupAnnotation; +import org.icepdf.ri.common.views.annotations.summary.AnnotationSummaryBox; +import org.icepdf.ri.common.views.annotations.summary.AnnotationSummaryComponent; +import org.icepdf.ri.common.views.annotations.summary.AnnotationSummaryGroup; +import org.icepdf.ri.common.views.annotations.summary.colorpanel.ColorLabelPanel; + +import javax.swing.*; +import java.awt.*; +import java.util.List; +import java.util.*; +import java.util.stream.Collectors; + +/** + * Class managing group operations + */ +public class GroupManager { + + private final ResourceBundle messageBundle; + private final SummaryController controller; + private final Map annotationGroups; + private final Map groups; + private final Map> namedGroups; + + public GroupManager(final SummaryController controller) { + this.controller = controller; + this.annotationGroups = new HashMap<>(); + this.groups = new HashMap<>(); + this.namedGroups = new HashMap<>(); + this.messageBundle = controller.getController().getMessageBundle(); + } + + private Collection getAllGroups() { + return namedGroups.values().stream().flatMap(m -> m.values().stream()).collect(Collectors.toList()); + } + + /** + * Refresh all the groups + */ + public void refreshGroups() { + for (final AnnotationSummaryGroup group : getAllGroups()) { + group.clear(); + } + for (final AnnotationSummaryGroup group : getAllGroups()) { + if (groups.get(group) != null) { + groups.get(group).addComponent(group); + } + } + } + + /** + * Adds the groups to their suitable panel + * + * @param colorLabelPanel The panel to add to + */ + public void addGroupsToPanel(final ColorLabelPanel colorLabelPanel) { + for (final AnnotationSummaryGroup group : namedGroups.getOrDefault(colorLabelPanel.getColorLabel().getColor(), + Collections.emptyMap()).values()) { + if (!groups.containsKey(group)) { + colorLabelPanel.addGroup(group, -1); + } + } + } + + /** + * Checks that a group exists + * + * @param c The color of the group + * @param name The name of the group + * @return True if it exists + */ + public boolean groupExists(final Color c, final String name) { + return namedGroups.containsKey(c) && namedGroups.get(c).containsKey(name); + } + + /** + * Gets a group given a color and a name + * + * @param c The color + * @param name The group name + * @return The group (or null) + */ + public AnnotationSummaryGroup getGroup(final Color c, final String name) { + return namedGroups.get(c).get(name); + } + + + public AnnotationSummaryGroup createGroup(final Collection components, final int y) { + if (components.stream().allMatch(c -> groups.get(c) == groups.get(components.iterator().next()))) { + final String name = showInsertGroupNameDialog(components.iterator().next().getColor(), ""); + return createGroup(components, y, name); + } else { + return null; + } + } + + public AnnotationSummaryGroup createGroup(final Collection components) { + return createGroup(components, -1); + } + + public AnnotationSummaryGroup createGroup(final Collection components, final String name) { + return createGroup(components, -1, name); + } + + /** + * Creates a group + * + * @param components The components composing the group + * @param y The y coordinate to insert the group at + * @param name The name of the group + */ + public AnnotationSummaryGroup createGroup(final Collection components, final int y, final String name) { + if (name == null) { + return null; + } else { + controller.setHasManuallyChanged(); + controller.getDragAndLinkManager().unlinkAll(components, false); + final AnnotationSummaryGroup group = new AnnotationSummaryGroup(components, name, controller); + final Map colorGroups = namedGroups.getOrDefault(group.getColor(), new HashMap<>()); + colorGroups.put(name, group); + namedGroups.put(group.getColor(), colorGroups); + components.stream().filter(AnnotationSummaryBox.class::isInstance).flatMap(c -> + c.getAnnotations().stream()).forEach(a -> annotationGroups.put(a.getPObjectReference(), group)); + final AnnotationSummaryGroup oldParent = groups.get(components.iterator().next()); + components.forEach(c -> groups.put(c, group)); + if (oldParent == null) { + final ColorLabelPanel panel = controller.getColorPanelFor(group); + if (panel != null) { + panel.addGroup(group, y); + } + } else { + groups.put(group, oldParent); + oldParent.addComponent(group); + } + components.forEach(c -> { + if (oldParent != null) { + oldParent.removeComponent(c); + } + }); + controller.refreshPanelLayout(); + return group; + } + } + + /** + * Deletes a group + * + * @param group The group to delete + */ + public void deleteGroup(final AnnotationSummaryGroup group) { + group.delete(); + disbandGroup(group); + controller.refreshPanelLayout(); + } + + /** + * Disbands a group + * + * @param group The group to disband + */ + public void disbandGroup(final AnnotationSummaryGroup group) { + controller.setHasManuallyChanged(); + namedGroups.getOrDefault(group.getColor(), new HashMap<>()).remove(group.getName()); + final ColorLabelPanel panel = controller.getColorPanelFor(group); + final AnnotationSummaryGroup parent = groups.get(group); + controller.getDragAndLinkManager().unlinkComponent(group, false); + if (parent != null) { + group.getSubComponents().forEach(c -> { + groups.put(c, parent); + if (c instanceof AnnotationSummaryBox) { + c.getAnnotations().forEach(a -> annotationGroups.put(a.getPObjectReference(), parent)); + } + parent.addComponent(c); + }); + } else { + final List components = group.getSubComponents(); + final int pos = panel.getDraggableAnnotationPanel().getPositionFor(group); + for (int i = 0; i < components.size(); ++i) { + final AnnotationSummaryComponent c = components.get(i); + groups.remove(c); + panel.addComponent(c, pos + i); + if (c instanceof AnnotationSummaryBox) { + c.getAnnotations().forEach(a -> annotationGroups.remove(a.getPObjectReference())); + } + } + } + removeFromGroup(group); + controller.refreshPanelLayout(); + } + + + private void updateGroupName(final AnnotationSummaryGroup group, final String oldname, final String newname) { + controller.setHasManuallyChanged(); + namedGroups.get(group.getColor()).put(newname, group); + namedGroups.get(group.getColor()).remove(oldname); + } + + private String showInsertGroupNameDialog(final Color color, final String basename) { + final String name = controller.showInputDialog(messageBundle.getString("viewer.summary.creategroup.dialog.label"), + messageBundle.getString("viewer.summary.creategroup.dialog.title"), JOptionPane.QUESTION_MESSAGE, basename, null); + if (name == null) { + return null; + } else if (namedGroups.getOrDefault(color, new HashMap<>()).containsKey(name) || name.isEmpty()) { + controller.showMessageDialog(messageBundle.getString("viewer.summary.creategroup.dialog.error.label"), + messageBundle.getString("viewer.summary.creategroup.dialog.error.title"), JOptionPane.ERROR_MESSAGE); + return showInsertGroupNameDialog(color, basename); + } else { + return name; + } + } + + /** + * Clears all the groups + */ + public void clear() { + final List allGroups = new ArrayList<>(getAllGroups()); + allGroups.forEach(this::disbandGroup); + annotationGroups.clear(); + groups.clear(); + namedGroups.clear(); + } + + /** + * Renames a group + * + * @param group The group to rename + */ + public void renameGroup(final AnnotationSummaryGroup group) { + final String oldname = group.getName(); + final String newname = showInsertGroupNameDialog(group.getColor(), oldname); + if (newname != null) { + updateGroupName(group, oldname, newname); + group.setName(newname); + } + } + + /** + * Returns the parent group of a component + * + * @param c The component + * @return The group containing the component, or null if it's at the root + */ + public AnnotationSummaryGroup getParentOf(final AnnotationSummaryComponent c) { + return groups.get(c); + } + + /** + * Returns the parent group of an annotation + * + * @param annot The annotation + * @return The group containing the annotation component, or null if it's at the root + */ + public AnnotationSummaryGroup getParentOf(final MarkupAnnotation annot) { + return annotationGroups.get(annot.getPObjectReference()); + } + + /** + * Removes a component from a group + * + * @param c The component to remove + */ + public void removeFromGroup(final AnnotationSummaryComponent c) { + final AnnotationSummaryGroup group = groups.remove(c); + if (c != null) { + if (group != null) { + group.removeComponent(c); + } else { + final ColorLabelPanel panel = controller.getColorPanelFor(c); + if (panel != null) panel.removeComponent(c); + } + } + if (c instanceof AnnotationSummaryBox) { + c.getAnnotations().forEach(a -> annotationGroups.remove(a.getPObjectReference())); + } + controller.refreshPanelLayout(); + } + + /** + * Moves a component out of a group + * + * @param c The component + */ + public void moveOut(final AnnotationSummaryComponent c) { + removeFromGroup(c); + final ColorLabelPanel panel = controller.getColorPanelFor(c); + if (panel != null) { + panel.addComponent(c); + } + controller.refreshPanelLayout(); + } + + /** + * Moves a component into a group + * + * @param c The component to move + * @param name The name of the group to put the component in + */ + public void moveIntoGroup(final AnnotationSummaryComponent c, final Color color, final String name) { + controller.setHasManuallyChanged(); + final AnnotationSummaryGroup oldGroup = groups.remove(c); + final AnnotationSummaryGroup group = namedGroups.get(color).get(name); + controller.getDragAndLinkManager().unlinkComponent(c, false); + if (!controller.isSingleDefaultColor() && !c.getColor().equals(group.getColor())) { + c.moveTo(group.getColor(), true); + } + groups.put(c, group); + if (c instanceof AnnotationSummaryBox) { + c.getAnnotations().forEach(a -> annotationGroups.put(a.getPObjectReference(), group)); + } + if (oldGroup != null) oldGroup.removeComponent(c); + group.addComponent(c); + controller.refreshPanelLayout(); + } + + public boolean canMoveTo(final AnnotationSummaryGroup group, final Color newColor) { + return !namedGroups.getOrDefault(newColor, Collections.emptyMap()).containsKey(group.getName()); + } + + public void moveTo(final AnnotationSummaryGroup group, final Color oldColor, final Color newColor) { + controller.setHasManuallyChanged(); + namedGroups.get(oldColor).remove(group.getName()); + final Map namedGroup = namedGroups.getOrDefault(newColor, new HashMap<>()); + namedGroup.put(group.getName(), group); + namedGroups.put(newColor, namedGroup); + } + + /** + * @return all the group names + */ + public Map> getGroupNames() { + return namedGroups.entrySet().stream().map(s -> new AbstractMap.SimpleEntry<>(s.getKey(), s.getValue().keySet())) + .collect(Collectors.toMap(AbstractMap.SimpleEntry::getKey, AbstractMap.SimpleEntry::getValue)); + } + + public Set getGroupNames(final Color c) { + return namedGroups.getOrDefault(c, new HashMap<>()).keySet(); + } +} diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/mainpanel/ImportExportHandler.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/mainpanel/ImportExportHandler.java new file mode 100644 index 000000000..8f3b3ae26 --- /dev/null +++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/mainpanel/ImportExportHandler.java @@ -0,0 +1,53 @@ +package org.icepdf.ri.common.views.annotations.summary.mainpanel; + +import org.icepdf.ri.common.views.annotations.summary.AnnotationSummaryComponent; +import org.icepdf.ri.common.views.annotations.summary.colorpanel.ColorLabelPanel; +import org.icepdf.ri.util.Pair; + +import java.io.InputStream; +import java.io.OutputStream; +import java.util.List; +import java.util.Map; + +/** + * Class managing the import and export of the summary window annotations + */ +public interface ImportExportHandler { + + /** + * Exports the information + * + * @param columns The columns to export + * @param output The outputstream to save to + * @throws Exception + */ + void exportFormat(final List columns, + final OutputStream output) throws Exception; + + /** + * Checks that a import is valid + * + * @param columns The columns to check against + * @param inputStream The input stream to import from + * @return A map of component to their position if the file is valid, null otherwise + * @throws Exception + */ + Map> validateImport(final List columns, + final InputStream inputStream, + final boolean partial) throws Exception; + + /** + * Imports a file to the summary + * + * @param compToCell The map of components to coordinates {@see validateImport} + * @param inputStream The inputStream of the file + * @throws Exception + */ + void importFormat(final Map> compToCell, + final InputStream inputStream) throws Exception; + + /** + * @return The file extension used by this importer-exporter + */ + String getFileExtension(); +} diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/mainpanel/LinkLabel.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/mainpanel/LinkLabel.java new file mode 100644 index 000000000..971f3f114 --- /dev/null +++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/mainpanel/LinkLabel.java @@ -0,0 +1,105 @@ +package org.icepdf.ri.common.views.annotations.summary.mainpanel; + +import org.icepdf.ri.images.Images; + +import javax.swing.*; +import java.awt.*; + +/** + * Class representing a label with a Link icon + */ +public class LinkLabel extends JLabel { + private final boolean isBottom; + private final boolean isFull; + private final boolean isLeft; + private boolean isDelete = false; + private static ImageIcon FULL; + private static ImageIcon FULL_DELETE; + private static ImageIcon HALF_RIGHT; + private static ImageIcon HALF_RIGHT_DELETE; + private static ImageIcon HALF_LEFT; + private static ImageIcon HALF_LEFT_DELETE; + + public LinkLabel(final boolean isLeft, final boolean isFull, final boolean isBottom) { + super(); + this.isBottom = isBottom; + this.isFull = isFull; + this.isLeft = isLeft; + setCorrectIcon(); + } + + + /** + * @return if the label is to be at the bottom of the component + */ + public boolean isBottom() { + return isBottom; + } + + /** + * @return if the label is to be the full image + */ + public boolean isFull() { + return isFull; + } + + /** + * @return if the label is to be to the left + */ + public boolean isLeft() { + return isLeft; + } + + public void toggleIcon() { + isDelete = !isDelete; + setCorrectIcon(); + } + + /** + * Sets the icon to be displayed + * + * @param delete If the icon to be displayed is the delete one + */ + public void setDeleteIcon(final boolean delete) { + isDelete = delete; + setCorrectIcon(); + } + + /** + * @return The current imageicon + */ + public ImageIcon getImageIcon() { + return (ImageIcon) getIcon(); + } + + private void setCorrectIcon() { + if (!isDelete) { + setIcon(isFull ? FULL : (isLeft ? HALF_LEFT : HALF_RIGHT)); + } else { + setIcon(isFull ? FULL_DELETE : (isLeft ? HALF_LEFT_DELETE : HALF_RIGHT_DELETE)); + } + } + + /** + * Rescales all images by the given factor + * + * @param factor The factor + */ + public static void rescaleAll(final int factor) { + FULL = rescale(new ImageIcon(Images.get("link.png")), factor); + FULL_DELETE = rescale(new ImageIcon(Images.get("unlink.png")), factor); + HALF_RIGHT = rescale(new ImageIcon(Images.get("link1.png")), factor); + HALF_RIGHT_DELETE = rescale(new ImageIcon(Images.get("unlink1.png")), factor); + HALF_LEFT = rescale(new ImageIcon(Images.get("link2.png")), factor); + HALF_LEFT_DELETE = rescale(new ImageIcon(Images.get("unlink2.png")), factor); + } + + private static ImageIcon rescale(final ImageIcon icon) { + return rescale(icon, 8); + } + + private static ImageIcon rescale(final ImageIcon icon, final int factor) { + return new ImageIcon(icon.getImage().getScaledInstance(icon.getIconWidth() / factor, + icon.getIconHeight() / factor, Image.SCALE_SMOOTH)); + } +} diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/mainpanel/NoOpImportExportHandler.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/mainpanel/NoOpImportExportHandler.java new file mode 100644 index 000000000..c9ab343bd --- /dev/null +++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/mainpanel/NoOpImportExportHandler.java @@ -0,0 +1,39 @@ +package org.icepdf.ri.common.views.annotations.summary.mainpanel; + +import org.icepdf.ri.common.views.annotations.summary.AnnotationSummaryComponent; +import org.icepdf.ri.common.views.annotations.summary.colorpanel.ColorLabelPanel; +import org.icepdf.ri.util.Pair; + +import java.io.InputStream; +import java.io.OutputStream; +import java.util.List; +import java.util.Map; + +/** + * An importer exporter that can't do anything + */ +public class NoOpImportExportHandler implements ImportExportHandler { + + @Override + public void exportFormat(final List columns, final OutputStream output) throws Exception { + throw new UnsupportedOperationException("No implementation"); + } + + @Override + public Map> validateImport(final List columns, + final InputStream inputStream, + final boolean partial) throws Exception { + return null; + } + + @Override + public void importFormat(final Map> compToCell, + final InputStream inputStream) throws Exception { + throw new UnsupportedOperationException("No implementation"); + } + + @Override + public String getFileExtension() { + return "empty"; + } +} diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/mainpanel/SummaryController.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/mainpanel/SummaryController.java new file mode 100644 index 000000000..0f0aebb4f --- /dev/null +++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/mainpanel/SummaryController.java @@ -0,0 +1,1191 @@ +package org.icepdf.ri.common.views.annotations.summary.mainpanel; + +import org.icepdf.core.pobjects.Document; +import org.icepdf.core.pobjects.Reference; +import org.icepdf.core.pobjects.annotations.Annotation; +import org.icepdf.core.pobjects.annotations.MarkupAnnotation; +import org.icepdf.core.util.PropertyConstants; +import org.icepdf.ri.common.MutableDocument; +import org.icepdf.ri.common.utility.annotation.properties.ValueLabelItem; +import org.icepdf.ri.common.views.Controller; +import org.icepdf.ri.common.views.annotations.MarkupAnnotationComponent; +import org.icepdf.ri.common.views.annotations.PopupAnnotationComponent; +import org.icepdf.ri.common.views.annotations.summary.AnnotationSummaryBox; +import org.icepdf.ri.common.views.annotations.summary.AnnotationSummaryComponent; +import org.icepdf.ri.common.views.annotations.summary.colorpanel.ColorLabelPanel; +import org.icepdf.ri.common.views.annotations.summary.colorpanel.DraggableAnnotationPanel; +import org.icepdf.ri.common.widgets.DragDropColorList; +import org.icepdf.ri.util.Pair; +import org.icepdf.ri.util.ViewerPropertiesManager; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ComponentAdapter; +import java.awt.event.ComponentEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.io.BufferedInputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.text.MessageFormat; +import java.util.List; +import java.util.*; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Collectors; + +/** + * Main controller + */ +public class SummaryController implements MutableDocument { + + private static final Logger LOG = + Logger.getLogger(SummaryController.class.toString()); + + private static final ImportExportHandler DEFAULT_IMPORT_EXPORT_HANDLER = new NoOpImportExportHandler(); + private static final DragDropColorList.ColorLabel DEFAULT_COLOR_LABEL = new DragDropColorList.ColorLabel(Color.WHITE, "default"); + + private final Frame frame; + private final Controller controller; + protected final DragAndLinkManager dragManager; + + private final AnnotationSummaryPanel summaryPanel; + + protected List annotationNamedColorPanels; + private int fontSize; + + private final Map annotationToBox; + private final Map annotationToColorPanel; + + private final GroupManager groupManager; + + private final MouseListener mouseListener; + + private final Map directLinks; + private final Map leftLinks; + private final Map rightLinks; + private final Map siblingLabels; + + private final List xCoordinates; + private final Map> yCoordinates; + + private boolean hasLoaded = false; + private boolean hasManuallyChanged = false; + private boolean hasAutomaticallyChanged = false; + + public SummaryController(final Frame frame, final Controller controller, final AnnotationSummaryPanel summaryPanel) { + this.frame = frame; + this.controller = controller; + this.summaryPanel = summaryPanel; + this.annotationNamedColorPanels = new ArrayList<>(); + this.annotationToBox = new HashMap<>(); + this.annotationToColorPanel = new HashMap<>(); + this.directLinks = new HashMap<>(); + this.leftLinks = new HashMap<>(); + this.rightLinks = new HashMap<>(); + this.siblingLabels = new HashMap<>(); + this.xCoordinates = new ArrayList<>(); + this.yCoordinates = new HashMap<>(); + this.dragManager = createDragAndLinkManager(); + this.groupManager = createGroupManager(); + this.mouseListener = new SummaryMouseListener(); + this.fontSize = new JLabel().getFont().getSize(); + LinkLabel.rescaleAll(192 / fontSize); + summaryPanel.addComponentListener(new SummaryComponentListener()); + controller.getDocumentViewController().getPropertyChangeSupport().addPropertyChangeListener(new PropertiesListener()); + } + + protected DragAndLinkManager createDragAndLinkManager() { + return new DragAndLinkManager(this); + } + + protected GroupManager createGroupManager() { + return new GroupManager(this); + } + + protected ImportExportHandler getImportExportHandler() { + return DEFAULT_IMPORT_EXPORT_HANDLER; + } + + public boolean canSave() { + return getImportExportHandler() != DEFAULT_IMPORT_EXPORT_HANDLER; + } + + public Frame getFrame() { + return frame; + } + + public void clear() { + dragManager.clear(); + groupManager.clear(); + xCoordinates.clear(); + yCoordinates.clear(); + } + + public void save() { + exportFormat(getDefaultSummaryOutputStream()); + } + + public void setHasManuallyChanged() { + setHasManuallyChanged(true); + } + + public void setHasManuallyChanged(final boolean changed) { + hasManuallyChanged = changed; + hasAutomaticallyChanged = changed; + } + + public void setHasAutomaticallyChanged(final boolean changed) { + hasAutomaticallyChanged = changed; + } + + public boolean hasManuallyChanged() { + return hasManuallyChanged; + } + + public boolean hasAutomaticallyChanged() { + return hasAutomaticallyChanged; + } + + public boolean hasChanged() { + return hasManuallyChanged || (hasLoaded && hasAutomaticallyChanged); + } + + public String getFontName() { + return ((ValueLabelItem) summaryPanel.getFontNameBox().getSelectedItem()).getLabel(); + } + + public boolean hasLoaded() { + return hasLoaded; + } + + + /** + * Exports the summary to a file + * + * @param output The output stream + */ + public void exportFormat(final OutputStream output) { + final ResourceBundle messageBundle = controller.getMessageBundle(); + try { + getImportExportHandler().exportFormat(annotationNamedColorPanels, output); + } catch (final Exception e) { + LOG.log(Level.SEVERE, e, () -> "Couldn't export summary"); + showMessageDialog(MessageFormat.format(messageBundle.getString("viewer.summary.export.failure.label") + .replace("'", "''") + , e.getMessage() == null ? e.toString() : e.getMessage()), + messageBundle.getString("viewer.summary.export.failure.title"), JOptionPane.ERROR_MESSAGE); + } finally { + try { + output.close(); + } catch (final IOException ignored) { + } + } + setHasManuallyChanged(false); + hasLoaded = true; + } + + /** + * Checks if a format file can be imported + * + * @param inputStream The inputStream + * @return The map if the file is compatible, null otherwise + */ + public Map> canImport(final InputStream inputStream, final boolean partial) { + try { + final Map> compToCell = + getImportExportHandler().validateImport(annotationNamedColorPanels, inputStream, partial); + return compToCell != null && (partial || compToCell.keySet().containsAll( + new HashSet<>(annotationToBox.values()))) ? compToCell : null; + } catch (final Exception e) { + return null; + } + } + + /** + * Imports the summary from a file + * + * @param inputStream The input stream + * @return true if the import was unsuccessful and the user wants to delete the file + */ + public boolean importFormat(final InputStream inputStream, final boolean partial) { + try { + final Map> compToCell = canImport(inputStream, partial); + inputStream.reset(); + return importFormat(compToCell, inputStream); + } catch (final IOException ignored) { + return false; + } + } + + public void setLoaded(final boolean hasLoaded) { + this.hasLoaded = hasLoaded; + } + + /** + * Imports the summary from a file + * + * @param compToCell The precomputed map of component to cell + * @param inputStream The input stream + * @return true if the import was unsuccessful and the user wants to delete the file + */ + public boolean importFormat(final Map> compToCell, + final InputStream inputStream) { + final ResourceBundle messageBundle = controller.getMessageBundle(); + try { + if (compToCell != null) { + clear(); + getImportExportHandler().importFormat(compToCell, inputStream); + refreshCoordinates(); + refreshLinks(); + setHasManuallyChanged(false); + hasLoaded = true; + return false; + } else if (!isEmpty()) { + return JOptionPane.showConfirmDialog(frame, + messageBundle.getString("viewer.summary.import.different.label"), + messageBundle.getString("viewer.summary.import.failure.title"), + JOptionPane.YES_NO_OPTION, JOptionPane.ERROR_MESSAGE) == JOptionPane.YES_OPTION; + } else { + return true; + } + } catch (final Exception e) { + LOG.log(Level.SEVERE, e, () -> "Error importing summary"); + return JOptionPane.showConfirmDialog(frame, + MessageFormat.format(messageBundle.getString("viewer.summary.import.failure.label").replace("'", "''"), + e.getMessage() == null ? e.toString() : e.getMessage()), + messageBundle.getString("viewer.summary.import.failure.title"), + JOptionPane.YES_NO_OPTION, JOptionPane.ERROR_MESSAGE) == JOptionPane.YES_OPTION; + } + } + + /** + * @return true if there are no annotations in the summary + */ + public boolean isEmpty() { + return annotationNamedColorPanels.isEmpty() || annotationNamedColorPanels.stream().allMatch(clp -> + clp.getNumberOfComponents() == 0); + } + + /** + * Compacts the panel by reducing the blank space + */ + void compactPanel() { + setHasManuallyChanged(); + final UUID uuid = UUID.randomUUID(); + annotationNamedColorPanels.forEach(clp -> clp.compact(uuid)); + } + + void addFontListeners() { + summaryPanel.getFontSizeBox().addItemListener(itemEvent -> { + final JComboBox fontSizeBox = (JComboBox) itemEvent.getSource(); + final ViewerPropertiesManager propertiesManager = controller.getPropertiesManager(); + final ValueLabelItem tmp = (ValueLabelItem) fontSizeBox.getSelectedItem(); + // fire the font size property change event. + if (tmp != null) { + final int value = (int) tmp.getValue(); + if (annotationNamedColorPanels != null) { + for (final ColorLabelPanel colorLabelPanel : annotationNamedColorPanels) { + colorLabelPanel.firePropertyChange(PropertyConstants.ANNOTATION_SUMMARY_BOX_FONT_SIZE_CHANGE, + 0, (float) value); + } + } + propertiesManager.setInt(ViewerPropertiesManager.PROPERTY_ANNOTATION_SUMMARY_FONT_SIZE, value); + setFontSize(value); + } + }); + summaryPanel.getFontNameBox().addItemListener(itemEvent -> { + final JComboBox fontNameBox = (JComboBox) itemEvent.getSource(); + final ValueLabelItem item = (ValueLabelItem) fontNameBox.getSelectedItem(); + final String familyName = item != null ? (String) item.getValue() : null; + if (annotationNamedColorPanels != null) { + for (final ColorLabelPanel colorLabelPanel : annotationNamedColorPanels) { + colorLabelPanel.updateFontFamily(familyName); + } + } + controller.getPropertiesManager().set(ViewerPropertiesManager.PROPERTY_ANNOTATION_SUMMARY_FONT_NAME, familyName); + }); + } + + public void forceChangeFontName(final String name) { + applySelectedValue(summaryPanel.getFontNameBox(), name); + } + + public void forceChangeFontSize(final int fontSize) { + applySelectedValue(summaryPanel.getFontSizeBox(), fontSize); + } + + public void dispatch(final MouseEvent e) { + summaryPanel.getAnnotationsPanel().dispatchEvent(SwingUtilities.convertMouseEvent((Component) e.getSource(), e, + summaryPanel.getAnnotationsPanel())); + } + + void addPotentialLinkMouseListener() { + final MouseAdapter potentialLinkMouseAdapter = new PotentialLinkMouseListener(); + summaryPanel.getAnnotationsPanel().addMouseMotionListener(potentialLinkMouseAdapter); + summaryPanel.getAnnotationsPanel().addMouseListener(potentialLinkMouseAdapter); + } + + public void setFontSize(final int fontSize) { + setHasManuallyChanged(); + this.fontSize = fontSize; + LinkLabel.rescaleAll(192 / fontSize); + final UUID uuid = UUID.randomUUID(); + annotationNamedColorPanels.forEach(c -> c.getDraggableAnnotationPanel().checkForOverlap(uuid)); + refreshLinks(); + } + + public int getFontSize() { + return summaryPanel.getFontSize(); + } + + public String getLabelFor(final Color c) { + final DragDropColorList.ColorLabel colorLabel = getColorLabelFor(c); + return colorLabel.getLabel(); + } + + /** + * Returns a ColorLabel for a given color + * + * @param c The color + * @return The colorlabel or null + */ + public DragDropColorList.ColorLabel getColorLabelFor(final Color c) { + for (final ColorLabelPanel panel : annotationNamedColorPanels) { + final DragDropColorList.ColorLabel colorLabel = panel.getColorLabel(); + if (colorLabel.getColor().equals(c)) { + return colorLabel; + } + } + return DEFAULT_COLOR_LABEL; + } + + /** + * Returns a ColorLabel for a given label + * + * @param s The label + * @return The colorlabel or null + */ + public DragDropColorList.ColorLabel getColorLabelFor(final String s) { + for (final ColorLabelPanel panel : annotationNamedColorPanels) { + final DragDropColorList.ColorLabel colorLabel = panel.getColorLabel(); + if (colorLabel.getLabel().equals(s)) { + return colorLabel; + } + } + return DEFAULT_COLOR_LABEL; + } + + public Color getColorFor(final String s) { + final DragDropColorList.ColorLabel colorLabel = getColorLabelFor(s); + return colorLabel.getColor(); + } + + public DragAndLinkManager getDragAndLinkManager() { + return dragManager; + } + + public GroupManager getGroupManager() { + return groupManager; + } + + public void showMessageDialog(final String content, final String title, final int type) { + JOptionPane.showMessageDialog(summaryPanel, content, title, type); + } + + public String showInputDialog(final String content, final String title, final int type, + final String initialValue, final Icon icon) { + return (String) JOptionPane.showInputDialog(summaryPanel, content, title, type, icon, null, initialValue); + } + + public void refreshPanelLayout() { + summaryPanel.refreshPanelLayout(); + } + + /** + * Refreshes the components coordinates cache + */ + void refreshCoordinates() { + xCoordinates.clear(); + yCoordinates.clear(); + if (summaryPanel.getAnnotationsPanel() != null) { + Arrays.stream(summaryPanel.getAnnotationsPanel().getComponents()).filter(ColorLabelPanel.class::isInstance).forEach(clp -> { + final DraggableAnnotationPanel dap = ((ColorLabelPanel) clp).getDraggableAnnotationPanel(); + final List yCoords = new ArrayList<>(); + Arrays.stream(dap.getComponents()).forEach(c -> { + yCoords.add(c.getY()); + yCoords.add(c.getY() + c.getHeight()); + }); + if (dap.getComponentCount() > 0) { + final Component firstComp = dap.getComponent(0); + xCoordinates.add(convertDragSpaceToSummarySpace(dap, firstComp.getX(), 0).x); + xCoordinates.add(convertDragSpaceToSummarySpace(dap, firstComp.getX() + firstComp.getWidth(), 0).x); + yCoordinates.put(((ColorLabelPanel) clp).getColorLabel().getColor(), yCoords.stream().map(y -> + convertDragSpaceToSummarySpace(dap, 0, y).y).collect(Collectors.toList())); + } + }); + } + } + + private Point convertDragSpaceToSummarySpace(final DraggableAnnotationPanel dap, final int x, final int y) { + final Point point = new Point(x, y); + return SwingUtilities.convertPoint(dap, point, summaryPanel.getAnnotationsPanel()); + } + + protected ColorLabelPanel createColorLabelPanel(final Frame frame, final DragDropColorList.ColorLabel colorLabel) { + return new ColorLabelPanel(frame, colorLabel, this); + } + + @Override + public void refreshDocumentInstance() { + if (controller.getDocument() != null) { + // get the named colour and build out the draggable panels. + final Document document = controller.getDocument(); + final List colorLabels = DragDropColorList.retrieveColorLabels(); + final int numberOfPanels = colorLabels.size(); + if (annotationNamedColorPanels != null) annotationNamedColorPanels.clear(); + annotationNamedColorPanels = new ArrayList<>(numberOfPanels); + annotationToColorPanel.clear(); + annotationToBox.clear(); + + groupManager.refreshGroups(); + + if (colorLabels.isEmpty()) { + // just one big panel with all the named colors. + // Create a dummy colorLabel to avoid null checks everywhere + final DragDropColorList.ColorLabel label = DEFAULT_COLOR_LABEL; + final ColorLabelPanel annotationColumnPanel = createColorLabelPanel(frame, label); + annotationNamedColorPanels.add(annotationColumnPanel); + for (int i = 0, max = document.getNumberOfPages(); i < max; i++) { + final List annotations = document.getPageTree().getPage(i).getAnnotations(); + if (annotations != null) { + final List copy = List.copyOf(annotations); + for (final Annotation annotation : copy) { + if (annotation instanceof MarkupAnnotation) { + annotationToColorPanel.put(annotation.getPObjectReference(), annotationColumnPanel); + final AnnotationSummaryBox box = annotationColumnPanel.addAnnotation((MarkupAnnotation) annotation); + if (box != null) { + annotationToBox.put(annotation.getPObjectReference(), box); + } + } + } + } + } + } else { + // build a panel for each color + for (final DragDropColorList.ColorLabel colorLabel : colorLabels) { + final ColorLabelPanel annotationColumnPanel = createColorLabelPanel(frame, colorLabel); + annotationNamedColorPanels.add(annotationColumnPanel); + annotationColumnPanel.addMouseListener(mouseListener); + for (int i = 0, max = document.getNumberOfPages(); i < max; i++) { + final List annots = document.getPageTree().getPage(i).getAnnotations(); + final List annotations = annots == null ? Collections.emptyList() : new ArrayList<>(annots); + for (final Annotation annotation : annotations) { + if (annotation instanceof MarkupAnnotation + && colorLabel.getColor().equals(annotation.getColor())) { + annotationToColorPanel.put(annotation.getPObjectReference(), annotationColumnPanel); + final AnnotationSummaryBox box = annotationColumnPanel.addAnnotation((MarkupAnnotation) annotation); + if (box != null) { + annotationToBox.put(annotation.getPObjectReference(), box); + } + } + } + } + groupManager.addGroupsToPanel(annotationColumnPanel); + } + } + } + refreshPanelLayout(); + tryImportSummaryFile(); + } + + private void tryImportSummaryFile() { + try (final InputStream inputStream = getDefaultSummaryInputStream()) { + if (inputStream != null) { + if (importFormat(inputStream, true)) { + deleteSummaryFile(); + } + } + } catch (final IOException e) { + LOG.log(Level.SEVERE, "Error getting default input stream", e); + } + setHasManuallyChanged(false); + } + + public OutputStream getDefaultSummaryOutputStream() { + final String file = getDefaultSummaryFile(); + if (file == null) { + return null; + } else { + try { + return Files.newOutputStream(Paths.get(file)); + } catch (final IOException e) { + LOG.log(Level.SEVERE, "Error getting output to " + file, e); + return null; + } + } + } + + public InputStream getDefaultSummaryInputStream() { + final String file = getDefaultSummaryFile(); + if (file == null) { + return null; + } else { + final Path path = Paths.get(file); + if (Files.exists(path)) { + try { + return new BufferedInputStream(Files.newInputStream(path)); + } catch (final IOException e) { + LOG.log(Level.SEVERE, "Error getting input from " + file, e); + return null; + } + } else { + return null; + } + } + } + + public void deleteSummaryFile() { + final String file = getDefaultSummaryFile(); + if (file != null) { + try { + Files.deleteIfExists(Paths.get(file)); + } catch (final IOException e) { + LOG.log(Level.SEVERE, "Error deleting " + file, e); + } + } + } + + public String getDefaultSummaryFile() { + if (controller.getDocument() == null) { + return null; + } else { + final String location = controller.getDocument().getDocumentLocation(); + final String folder = location.substring(0, location.lastIndexOf('/')); + final String filename = location.substring(location.lastIndexOf('/') + 1, location.lastIndexOf('.')); + return folder + File.separator + filename + '.' + DEFAULT_IMPORT_EXPORT_HANDLER.getFileExtension(); + } + } + + /** + * Gets the color panel for a given annotation + * + * @param annot The annotation + * @param cached If the result is to be retrieved from the cache or through an exhaustive search + * @return The color panel or null + */ + ColorLabelPanel getColorPanelFor(final MarkupAnnotation annot, final boolean cached) { + if (cached && annotationToColorPanel.containsKey(annot.getPObjectReference())) { + return annotationToColorPanel.get(annot.getPObjectReference()); + } else { + final List colorLabels = DragDropColorList.retrieveColorLabels(); + if (!colorLabels.isEmpty() && annotationNamedColorPanels != null) { + for (final ColorLabelPanel annotationColumnPanel : annotationNamedColorPanels) { + if (annotationColumnPanel.getColorLabel().getColor().equals(annot.getColor())) { + return annotationColumnPanel; + } + } + } + return getDefaultColorPanel(); + } + } + + /** + * Gets the color panel for a given component + * + * @param component The component + * @return The color panel, or null + */ + ColorLabelPanel getColorPanelFor(final AnnotationSummaryComponent component) { + return getColorPanelFor(component.getColor()); + } + + /** + * Gets the color panel for a given color + * + * @param color The color + * @return The colorpanel, or null + */ + ColorLabelPanel getColorPanelFor(final Color color) { + if (color != null) { + final Color withoutAlpha = new Color(color.getRed(), color.getGreen(), color.getBlue()); + for (final ColorLabelPanel p : annotationNamedColorPanels) { + if (p.getColorLabel().getColor().equals(withoutAlpha)) { + return p; + } + } + } + return getDefaultColorPanel(); + } + + private ColorLabelPanel getDefaultColorPanel() { + if (isSingleDefaultColor()) { + return annotationNamedColorPanels.get(0); + } else { + return null; + } + } + + /** + * Get the visible color to the left of a color panel + * + * @param panel The panel + * @return The color or null if the panel is the leftmost one + */ + public Color getLeftColor(final DraggableAnnotationPanel panel) { + return getColorForIdx(idxOf(panel) - 1); + } + + /** + * Get the visible color to the right of a color panel + * + * @param panel The panel + * @return The color or null if the panel is the rightmost one + */ + public Color getRightColor(final DraggableAnnotationPanel panel) { + return getColorForIdx(idxOf(panel) + 1); + } + + private int idxOf(final DraggableAnnotationPanel panel) { + final List components = Arrays.asList(summaryPanel.getAnnotationsPanel().getComponents()); + return components.stream().filter(ColorLabelPanel.class::isInstance).map(c -> ((ColorLabelPanel) c) + .getDraggableAnnotationPanel()).collect(Collectors.toList()).indexOf(panel); + } + + private Color getColorForIdx(final int idx) { + final List components = Arrays.stream(summaryPanel.getAnnotationsPanel().getComponents()) + .filter(ColorLabelPanel.class::isInstance).collect(Collectors.toList()); + return idx >= 0 && idx < components.size() ? ((ColorLabelPanel) components.get(idx)).getColorLabel().getColor() : null; + } + + public boolean isSingleDefaultColor() { + return annotationNamedColorPanels.size() == 1 && annotationNamedColorPanels.get(0).getColorLabel() == DEFAULT_COLOR_LABEL; + } + + static void applySelectedValue(final JComboBox comboBox, final Object value) { + ValueLabelItem currentItem; + for (int i = 0; i < comboBox.getItemCount(); i++) { + currentItem = comboBox.getItemAt(i); + if (currentItem.getValue().equals(value)) { + comboBox.setSelectedIndex(i); + break; + } + } + } + + @Override + public void disposeDocument() { + annotationNamedColorPanels.clear(); + } + + public Controller getController() { + return controller; + } + + public List getAnnotationNamedColorPanels() { + return annotationNamedColorPanels; + } + + + /** + * Moves a component to another color + * + * @param c The component to move + * @param color The new color + * @param oldColor The old color + */ + public void moveTo(final AnnotationSummaryComponent c, final Color color, final Color oldColor) { + moveTo(c, color, oldColor, true); + } + + /** + * Moves a component to another color + * + * @param c The component to move + * @param color The new color + * @param oldColor The old color + */ + public void moveTo(final AnnotationSummaryComponent c, final Color color, final Color oldColor, final boolean keepY) { + dragManager.unlinkComponent(c, false); + groupManager.removeFromGroup(c); + final ColorLabelPanel oldPanel = getColorPanelFor(oldColor); + if (oldPanel != null) { + oldPanel.removeComponent(c); + } + final ColorLabelPanel panel = getColorPanelFor(color); + if (panel != null) { + panel.addComponent(c, keepY ? c.asComponent().getY() : -1); + } + refreshPanelLayout(); + } + + boolean isLeftOf(final Color left, final Color right) { + final int leftIdx = idxOf(getColorPanelFor(left).getDraggableAnnotationPanel()); + final int rightIdx = idxOf(getColorPanelFor(right).getDraggableAnnotationPanel()); + return leftIdx < rightIdx; + } + + boolean isDirectlyLeftOf(final Color left, final Color right) { + final int leftIdx = idxOf(getColorPanelFor(left).getDraggableAnnotationPanel()); + final int rightIdx = idxOf(getColorPanelFor(right).getDraggableAnnotationPanel()); + return leftIdx == rightIdx - 1; + } + + boolean isRightOf(final Color right, final Color left) { + final int leftIdx = idxOf(getColorPanelFor(left).getDraggableAnnotationPanel()); + final int rightIdx = idxOf(getColorPanelFor(right).getDraggableAnnotationPanel()); + return rightIdx > leftIdx; + } + + boolean isDirectlyRightOf(final Color right, final Color left) { + final int leftIdx = idxOf(getColorPanelFor(left).getDraggableAnnotationPanel()); + final int rightIdx = idxOf(getColorPanelFor(right).getDraggableAnnotationPanel()); + return rightIdx == leftIdx + 1; + } + + private int idxOf(final Color c) { + for (int i = 0; i < annotationNamedColorPanels.size(); ++i) { + if (annotationNamedColorPanels.get(i).getColorLabel().getColor().equals(c)) { + return i; + } + } + return -1; + } + + /** + * Refreshes all the links + */ + public void refreshLinks() { + directLinks.values().forEach(summaryPanel::removeLabel); + leftLinks.values().forEach(summaryPanel::removeLabel); + rightLinks.values().forEach(summaryPanel::removeLabel); + directLinks.clear(); + leftLinks.clear(); + rightLinks.clear(); + siblingLabels.clear(); + final Map> linkedComponents = dragManager.getLinkedComponents(); + final List keys = linkedComponents.keySet().stream().sorted(Comparator.comparingInt(asc -> + idxOf(asc.getColor()))).collect(Collectors.toList()); + for (final AnnotationSummaryComponent key : keys) { + final List values = linkedComponents.get(key).stream().sorted(Comparator.comparingInt(asc -> + idxOf(asc.getColor()))).collect(Collectors.toList()); + for (final AnnotationSummaryComponent value : values) { + if (isDirectlyLeftOf(key.getColor(), value.getColor())) { + if (!directLinks.containsKey(key)) { + final LinkLabel label = new LinkLabel(false, true, isBottom(key, value, true, true)); + label.addMouseListener(new LinkMouseListener(label, key, value)); + summaryPanel.addLabel(key, label); + directLinks.put(key, label); + } + } else if (isLeftOf(key.getColor(), value.getColor())) { + if (!rightLinks.containsKey(key) && !leftLinks.containsKey(value)) { + addLabels(key, value, true); + } + } + } + } + summaryPanel.refreshAnnotationPanel(); + } + + + private void addLabels(final AnnotationSummaryComponent key, final AnnotationSummaryComponent value, final boolean left) { + final boolean isBottom = isBottom(key, value, false, left); + final LinkLabel keyLabel = new LinkLabel(!left, false, isBottom); + keyLabel.addMouseListener(new LinkMouseListener(keyLabel, key, value)); + final LinkLabel valueLabel = new LinkLabel(left, false, isBottom); + valueLabel.addMouseListener(new LinkMouseListener(valueLabel, value, key)); + summaryPanel.addLabel(key, keyLabel); + summaryPanel.addLabel(value, valueLabel); + if (left) { + rightLinks.put(key, keyLabel); + leftLinks.put(value, valueLabel); + } else { + leftLinks.put(key, keyLabel); + rightLinks.put(value, valueLabel); + } + siblingLabels.put(keyLabel, valueLabel); + siblingLabels.put(valueLabel, keyLabel); + } + + private boolean isBottom(final AnnotationSummaryComponent key, final AnnotationSummaryComponent value, final boolean full, final boolean left) { + return (full && ((rightLinks.containsKey(key) && !rightLinks.get(key).isBottom()) || + (leftLinks.containsKey(value) && !leftLinks.get(value).isBottom()))) || + (!full && left && ((directLinks.containsKey(key) && !directLinks.get(key).isBottom()) || + (rightLinks.containsKey(value) && !rightLinks.get(value).isBottom()))); + } + + /** + * Indicates that a component has moved + * + * @param comp The component that moved + */ + public void componentMoved(final AnnotationSummaryComponent comp) { + moveLabel(comp, leftLinks); + moveLabel(comp, rightLinks); + moveLabel(comp, directLinks); + summaryPanel.revalidate(); + summaryPanel.repaint(); + refreshCoordinates(); + } + + private void moveLabel(final AnnotationSummaryComponent comp, final Map map) { + if (map.containsKey(comp)) { + final LinkLabel label = map.get(comp); + final Component c = comp.asComponent(); + summaryPanel.updateLabel(c, label); + } + } + + void showAllHeaders() { + setAllHeaders(true); + } + + void hideAllHeaders() { + setAllHeaders(false); + } + + void setAllHeaders(final boolean visible) { + setHasManuallyChanged(); + annotationNamedColorPanels.forEach(clp -> { + final DraggableAnnotationPanel dap = clp.getDraggableAnnotationPanel(); + Arrays.stream(dap.getComponents()).forEach(c -> { + if (c instanceof AnnotationSummaryComponent) { + ((AnnotationSummaryComponent) c).setHeaderVisibility(visible); + } + }); + SwingUtilities.invokeLater(() -> dap.checkForOverlap(UUID.randomUUID())); + }); + } + + private class PotentialLinkMouseListener extends MouseAdapter { + + private LinkLabel currentLabel; + private AnnotationSummaryComponent c1; + private AnnotationSummaryComponent c2; + + @Override + public void mouseClicked(final MouseEvent e) { + if (currentLabel != null && currentLabel.isVisible() && c1 != null && c2 != null) { + dragManager.linkComponents(c1, c2); + if (c1.asComponent().getY() != c2.asComponent().getY()) { + getColorPanelFor(c2).getDraggableAnnotationPanel().moveComponentToY(c2.asComponent(), c1.asComponent().getY(), UUID.randomUUID()); + } + removeLabel(); + } + } + + @Override + public void mouseMoved(final MouseEvent e) { + final int x = e.getX(); + final int y = e.getY(); + int xIdx = Collections.binarySearch(xCoordinates, x); + if (xIdx < 0) { + xIdx = -1 * (xIdx + 1); + if (xIdx > 0 && xIdx < xCoordinates.size() && xIdx % 2 == 0) { + final Color color1 = getColorForIdx(xIdx / 2 - 1); + final AnnotationSummaryComponent comp1 = getComponentForY(color1, y); + if (comp1 == null) { + removeLabel(); + } else { + final Color color2 = getColorForIdx(xIdx / 2); + final AnnotationSummaryComponent comp2 = getComponentForY(color2, y); + if (comp2 == null) { + removeLabel(); + } else { + this.c1 = comp1; + this.c2 = comp2; + if (!dragManager.areLinked(c1, color2) && !dragManager.areLinked(c2, color1)) { + final int labelX = xCoordinates.get(xIdx - 1); + final AnnotationSummaryComponent yComp = c1.asComponent().getY() > c2.asComponent().getY() ? c1 : c2; + final int labelY = SwingUtilities.convertPoint(getColorPanelFor(yComp.getColor()).getDraggableAnnotationPanel(), + new Point(yComp.asComponent().getX(), yComp.asComponent().getY()), summaryPanel.getAnnotationsPanel()).y; + if (currentLabel == null) { + currentLabel = new LinkLabel(true, true, true); + summaryPanel.addAbsolute(summaryPanel.annotationsPanel, currentLabel, labelX, labelY, + currentLabel.getImageIcon().getIconWidth(), currentLabel.getImageIcon().getIconHeight()); + } else { + currentLabel.setLocation(labelX - (int) (1f / 3 * currentLabel.getImageIcon().getIconWidth()), labelY); + currentLabel.setVisible(true); + } + summaryPanel.refreshAnnotationPanel(); + } + } + } + + } else { + removeLabel(); + } + } else { + removeLabel(); + } + + } + + private void removeLabel() { + if (currentLabel != null) { + currentLabel.setVisible(false); + c1 = null; + c2 = null; + } + } + + private AnnotationSummaryComponent getComponentForY(final Color color, final int y) { + if (color != null) { + final List ycoords = yCoordinates.get(color); + int yIdx = Collections.binarySearch(ycoords, y); + if (yIdx < 0) { + yIdx = -1 * (yIdx + 1); + if (yIdx > 0 && yIdx < ycoords.size() && yIdx % 2 == 1) { + final int cIdx = yIdx / 2; + final DraggableAnnotationPanel annotationPanel = getColorPanelFor(color).getDraggableAnnotationPanel(); + return annotationPanel.getComponentCount() > cIdx ? + (AnnotationSummaryComponent) annotationPanel.getComponents()[cIdx] : null; + } + } + } + return null; + } + } + + /** + * Moves a component to a given y position + * + * @param comp The component to move + * @param y The y coordinate + * @param uuid The uuid of the operation + */ + public void moveComponentToY(final AnnotationSummaryComponent comp, final int y, final UUID uuid) { + final DraggableAnnotationPanel panel = getColorPanelFor(comp).getDraggableAnnotationPanel(); + panel.moveComponentToY(comp.asComponent(), y, false, uuid); + } + + private class LinkMouseListener extends MouseAdapter { + + private final LinkLabel label; + private final AnnotationSummaryComponent key; + private final AnnotationSummaryComponent value; + + public LinkMouseListener(final LinkLabel label, final AnnotationSummaryComponent key, + final AnnotationSummaryComponent value) { + this.label = label; + this.key = key; + this.value = value; + } + + @Override + public void mouseClicked(final MouseEvent e) { + dragManager.unlinkComponents(key, value); + } + + @Override + public void mouseEntered(final MouseEvent e) { + label.setDeleteIcon(true); + if (siblingLabels.containsKey(label)) { + siblingLabels.get(label).setDeleteIcon(true); + } + } + + @Override + public void mouseExited(final MouseEvent e) { + label.setDeleteIcon(false); + if (siblingLabels.containsKey(label)) { + siblingLabels.get(label).setDeleteIcon(false); + } + } + } + + private class PropertiesListener implements PropertyChangeListener { + protected MarkupAnnotation lastSelectedMarkupAnnotation; + + @Override + public void propertyChange(final PropertyChangeEvent evt) { + final Object newValue = evt.getNewValue(); + final Object oldValue = evt.getOldValue(); + final String propertyName = evt.getPropertyName(); + switch (propertyName) { + case PropertyConstants.ANNOTATION_DELETED: + if (oldValue instanceof MarkupAnnotationComponent) { + // find an remove the markup annotation node. + hasAutomaticallyChanged = true; + final MarkupAnnotationComponent comp = (MarkupAnnotationComponent) oldValue; + final MarkupAnnotation markupAnnotation = (MarkupAnnotation) comp.getAnnotation(); + final Reference ref = markupAnnotation.getPObjectReference(); + if (ref != null) { + AnnotationSummaryBox box = annotationToBox.get(ref); + if (box == null) { + if (comp.getPopupAnnotationComponent() != null && + comp.getPopupAnnotationComponent().getAnnotation() != null) { + box = annotationToBox.get(comp.getPopupAnnotationComponent() + .getAnnotation().getPObjectReference()); + } + } + if (box == null) { + if (comp.getPopupAnnotationComponent() != null && + comp.getPopupAnnotationComponent().getAnnotationParentComponent() != null && + comp.getPopupAnnotationComponent().getAnnotationParentComponent().getAnnotation() != null) { + box = + annotationToBox.get(comp.getPopupAnnotationComponent() + .getAnnotationParentComponent().getAnnotation().getPObjectReference()); + } + } + if (box == null) { + final ColorLabelPanel panel = getColorPanelFor(markupAnnotation, true); + if (panel != null) { + panel.removeAnnotation(markupAnnotation); + } + } else { + box.delete(); + dragManager.unlinkComponent(box, false); + } + annotationToBox.remove(ref); + annotationToColorPanel.remove(ref); + } + } + break; + case PropertyConstants.ANNOTATION_UPDATED: + final MarkupAnnotation annotation = newValue instanceof PopupAnnotationComponent ? + ((PopupAnnotationComponent) newValue).getAnnotation().getParent() : + newValue instanceof MarkupAnnotationComponent ? + (MarkupAnnotation) ((MarkupAnnotationComponent) newValue).getAnnotation() : null; + if (annotation != null) { + hasAutomaticallyChanged = true; + final ColorLabelPanel oldPanel = getColorPanelFor(annotation, true); + final ColorLabelPanel newPanel = getColorPanelFor(annotation, false); + final AnnotationSummaryBox box = annotationToBox.get(annotation.getPObjectReference()); + if (box != null) { + if (!oldPanel.equals(newPanel)) { + dragManager.unlinkComponent(box, false); + box.moveToCorrectPanel(); + } + box.refresh(); + } else { + if (oldPanel != newPanel || (oldPanel != null && !oldPanel.contains(annotation))) { + if (oldPanel != null) oldPanel.removeAnnotation(annotation); + if (newPanel != null) { + final AnnotationSummaryBox newBox = newPanel.addAnnotation(annotation); + if (newBox != null) { + annotationToBox.put(annotation.getPObjectReference(), newBox); + } + } + annotationToColorPanel.put(annotation.getPObjectReference(), newPanel); + } else { + if (newPanel != null) newPanel.updateAnnotation(annotation); + } + } + refreshPanelLayout(); + } + break; + case PropertyConstants.ANNOTATION_ADDED: + // rebuild the tree so we get a good sort etc and do worker thread setup. + if (newValue instanceof PopupAnnotationComponent) { + hasAutomaticallyChanged = true; + // find an remove the markup annotation node. + if (annotationNamedColorPanels != null) { + final PopupAnnotationComponent comp = (PopupAnnotationComponent) newValue; + final MarkupAnnotation markupAnnotation = comp.getAnnotation().getParent(); + if (markupAnnotation != null) { + final ColorLabelPanel panel = getColorPanelFor(markupAnnotation, false); + if (panel != null) { + final AnnotationSummaryBox box = panel.addAnnotation(markupAnnotation); + if (box != null) { + annotationToBox.put(markupAnnotation.getPObjectReference(), box); + } + annotationToColorPanel.put(markupAnnotation.getPObjectReference(), panel); + refreshPanelLayout(); + } + } + } + } + break; + case PropertyConstants.ANNOTATION_QUICK_COLOR_CHANGE: + if (lastSelectedMarkupAnnotation != null) { + // find and remove, + hasAutomaticallyChanged = true; + final AnnotationSummaryBox box = annotationToBox.get(lastSelectedMarkupAnnotation.getPObjectReference()); + if (box != null) { + dragManager.unlinkComponent(box, false); + box.moveToCorrectPanel(); + } else if (annotationNamedColorPanels != null) { + for (final ColorLabelPanel annotationColumnPanel : annotationNamedColorPanels) { + annotationColumnPanel.removeAnnotation(lastSelectedMarkupAnnotation); + refreshPanelLayout(); + } + // and then add back in. + final ColorLabelPanel panel = getColorPanelFor(lastSelectedMarkupAnnotation, false); + if (panel != null) { + annotationToColorPanel.put(lastSelectedMarkupAnnotation.getPObjectReference(), panel); + final AnnotationSummaryBox newBox = panel.addAnnotation(lastSelectedMarkupAnnotation); + if (newBox != null) { + annotationToBox.put(lastSelectedMarkupAnnotation.getPObjectReference(), newBox); + } + } + } + refreshPanelLayout(); + } + break; + case PropertyConstants.ANNOTATION_COLOR_PROPERTY_PANEL_CHANGE: + // no choice but to do a full refresh, order will be lost. + refreshDocumentInstance(); + hasAutomaticallyChanged = true; + break; + case PropertyConstants.ANNOTATION_SELECTED: + case PropertyConstants.ANNOTATION_FOCUS_GAINED: + if (newValue instanceof MarkupAnnotationComponent) { + lastSelectedMarkupAnnotation = (MarkupAnnotation) ((MarkupAnnotationComponent) newValue).getAnnotation(); + } + break; + case PropertyConstants.ANNOTATION_SUMMARY_UPDATED: + hasAutomaticallyChanged = true; + break; + default: + + } + } + } + + private class SummaryMouseListener extends MouseAdapter { + @Override + public void mouseClicked(final MouseEvent e) { + if (e.getClickCount() == 2) { + final Component comp = (Component) e.getSource(); + if (annotationNamedColorPanels != null) { + final double weightX = 1.0 / (float) annotationNamedColorPanels.size(); + final GridBagLayout gridBagLayout = (GridBagLayout) summaryPanel.getAnnotationsPanel().getLayout(); + for (final ColorLabelPanel colorLabelPanel : annotationNamedColorPanels) { + final GridBagConstraints constraints = gridBagLayout.getConstraints(colorLabelPanel); + constraints.weightx = colorLabelPanel.equals(comp) ? 0.9 : weightX; + gridBagLayout.setConstraints(colorLabelPanel, constraints); + colorLabelPanel.invalidate(); + } + summaryPanel.revalidate(); + } + } + } + } + + private class SummaryComponentListener extends ComponentAdapter { + @Override + public void componentResized(final ComponentEvent e) { + // reset the constraint back to an even division of + if (annotationNamedColorPanels != null) { + final double weightX = 1.0 / (float) annotationNamedColorPanels.size(); + final GridBagLayout gridBagLayout = (GridBagLayout) summaryPanel.getAnnotationsPanel().getLayout(); + for (final ColorLabelPanel colorLabelPanel : annotationNamedColorPanels) { + final GridBagConstraints constraints = gridBagLayout.getConstraints(colorLabelPanel); + constraints.weightx = weightX; + gridBagLayout.setConstraints(colorLabelPanel, constraints); + } + summaryPanel.invalidate(); + summaryPanel.revalidate(); + } + } + } +} diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/menu/BoxMenuFactory.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/menu/BoxMenuFactory.java new file mode 100644 index 000000000..1b5714969 --- /dev/null +++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/menu/BoxMenuFactory.java @@ -0,0 +1,83 @@ +package org.icepdf.ri.common.views.annotations.summary.menu; + +import org.icepdf.core.pobjects.annotations.Annotation; +import org.icepdf.ri.common.views.Controller; +import org.icepdf.ri.common.views.annotations.MarkupAnnotationComponent; +import org.icepdf.ri.common.views.annotations.summary.AnnotationSummaryBox; +import org.icepdf.ri.common.views.annotations.summary.AnnotationSummaryComponent; +import org.icepdf.ri.common.views.annotations.summary.colorpanel.DraggablePanelController; +import org.icepdf.ri.common.views.annotations.summary.mainpanel.SummaryController; + +import javax.swing.*; +import java.awt.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.ResourceBundle; +import java.util.stream.Collectors; + + +/** + * The summary view is made up of annotation contents for markup annotations. The view however is built independently + * of the the page view and the component state may not be in correct state to use the default MarkupAnnotationPopupMenu + *

+ * This class takes into account that the component state is not guaranteed. + * + * @since 6.3 + *

+ *

+ * Allows creating a JPopupMenu or a JMenu depending on the context + */ +public final class BoxMenuFactory { + + private BoxMenuFactory() { + } + + public static JMenu createBoxMenu(final AnnotationSummaryBox annotationSummaryBox, + final MarkupAnnotationComponent annotationComponent, final Frame frame, + final DraggablePanelController panel, final SummaryController summaryController, + final String name) { + final JMenu menu = new JMenu(name); + buildGui(annotationSummaryBox, annotationComponent, frame, panel, summaryController).forEach(menu::add); + return menu; + } + + public static JPopupMenu createBoxPopupMenu(final AnnotationSummaryBox annotationSummaryBox, + final MarkupAnnotationComponent annotationComponent, final Frame frame, + final DraggablePanelController panel, final SummaryController summaryController) { + final JPopupMenu menu = new JPopupMenu(); + buildGui(annotationSummaryBox, annotationComponent, frame, panel, summaryController).forEach(menu::add); + return menu; + } + + public static List buildGui(final AnnotationSummaryBox annotationSummaryBox, final MarkupAnnotationComponent annotationComponent, + final Frame frame, final DraggablePanelController panel, final SummaryController summaryController) { + final List list = new ArrayList<>(); + final Controller controller = summaryController.getController(); + final MenuFactoryHelper helper = new MenuFactoryHelper(summaryController, panel, Arrays.asList(new AnnotationSummaryBox[]{annotationSummaryBox})); + final ResourceBundle messageBundle = controller.getMessageBundle(); + if (summaryController.getGroupManager().getParentOf(annotationSummaryBox) != null) { + final JMenuItem moveOutItem = helper.getMoveOutMenuItem(); + list.add(moveOutItem); + } + helper.addCommonMenu(list); + final JMenuItem propertiesMenuItem = new JMenuItem(messageBundle.getString("viewer.annotation.popup.properties.label")); + propertiesMenuItem.addActionListener(e -> controller.showAnnotationProperties(annotationComponent)); + list.add(propertiesMenuItem); + propertiesMenuItem.addActionListener(e -> { + controller.showAnnotationProperties(annotationComponent, frame); + summaryController.refreshDocumentInstance(); + }); + final JMenuItem createGroupMenuItem = new JMenuItem(messageBundle.getString("viewer.summary.popup.group.label")); + createGroupMenuItem.addActionListener(e -> { + final List components = new ArrayList<>(); + components.add(annotationSummaryBox); + final List cComponents = components.stream().map(AnnotationSummaryComponent::asComponent).collect(Collectors.toList()); + summaryController.getGroupManager().createGroup(components, panel.getPanel().getFirstPosForComponents(cComponents)); + }); + list.add(createGroupMenuItem); + return helper.addLinkMenu(list, summaryController.getGroupManager().getParentOf(annotationSummaryBox), summaryController.getDragAndLinkManager().isLinked(annotationSummaryBox)); + } + + +} diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/menu/GroupMenuFactory.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/menu/GroupMenuFactory.java new file mode 100644 index 000000000..a9862d092 --- /dev/null +++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/menu/GroupMenuFactory.java @@ -0,0 +1,56 @@ +package org.icepdf.ri.common.views.annotations.summary.menu; + +import org.icepdf.ri.common.views.annotations.summary.AnnotationSummaryGroup; +import org.icepdf.ri.common.views.annotations.summary.colorpanel.DraggablePanelController; +import org.icepdf.ri.common.views.annotations.summary.mainpanel.GroupManager; +import org.icepdf.ri.common.views.annotations.summary.mainpanel.SummaryController; + +import javax.swing.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.ResourceBundle; + +/** + * Creates a JPopupMenu or a JMenu for a group depending on the context + */ +public final class GroupMenuFactory { + + private GroupMenuFactory() { + } + + public static JMenu createGroupMenu(final AnnotationSummaryGroup group, final SummaryController summaryController, + final DraggablePanelController panel, final String name) { + final JMenu menu = new JMenu(name); + buildGui(group, summaryController, panel).forEach(menu::add); + return menu; + } + + public static JPopupMenu createGroupPopupMenu(final AnnotationSummaryGroup group, final SummaryController summaryController, + final DraggablePanelController panel) { + final JPopupMenu menu = new JPopupMenu(); + buildGui(group, summaryController, panel).forEach(menu::add); + return menu; + } + + public static List buildGui(final AnnotationSummaryGroup group, final SummaryController summaryController, + final DraggablePanelController panel) { + final MenuFactoryHelper helper = new MenuFactoryHelper(summaryController, panel, Arrays.asList(new AnnotationSummaryGroup[]{group})); + final List list = new ArrayList<>(); + final ResourceBundle messageBundle = summaryController.getController().getMessageBundle(); + final GroupManager groupManager = summaryController.getGroupManager(); + final JMenuItem renameMenuItem = new JMenuItem(messageBundle.getString("viewer.summary.popup.group.rename")); + renameMenuItem.addActionListener(e -> groupManager.renameGroup(group)); + list.add(renameMenuItem); + final JMenuItem disbandMenuItem = new JMenuItem(messageBundle.getString("viewer.summary.popup.group.disband")); + disbandMenuItem.addActionListener(e -> groupManager.disbandGroup(group)); + list.add(disbandMenuItem); + if (groupManager.getParentOf(group) != null) { + final JMenuItem moveOutItem = helper.getMoveOutMenuItem(); + list.add(moveOutItem); + } + helper.addCommonMenu(list); + return helper.addLinkMenu(list, summaryController.getGroupManager().getParentOf(group), + summaryController.getDragAndLinkManager().isLinked(group)); + } +} diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/menu/MenuFactoryHelper.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/menu/MenuFactoryHelper.java new file mode 100644 index 000000000..88a0f2f77 --- /dev/null +++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/menu/MenuFactoryHelper.java @@ -0,0 +1,297 @@ +package org.icepdf.ri.common.views.annotations.summary.menu; + +import org.icepdf.core.pobjects.annotations.TextMarkupAnnotation; +import org.icepdf.ri.common.views.Controller; +import org.icepdf.ri.common.views.annotations.summary.AnnotationSummaryComponent; +import org.icepdf.ri.common.views.annotations.summary.AnnotationSummaryGroup; +import org.icepdf.ri.common.views.annotations.summary.colorpanel.DraggablePanelController; +import org.icepdf.ri.common.views.annotations.summary.mainpanel.DragAndLinkManager; +import org.icepdf.ri.common.views.annotations.summary.mainpanel.GroupManager; +import org.icepdf.ri.common.views.annotations.summary.mainpanel.SummaryController; +import org.icepdf.ri.common.widgets.DragDropColorList; + +import javax.swing.*; +import java.awt.*; +import java.text.MessageFormat; +import java.util.List; +import java.util.*; +import java.util.stream.Collectors; + +/** + * Helper class for creating menus + */ +public class MenuFactoryHelper { + private final Controller controller; + private final ResourceBundle messageBundle; + private final SummaryController summaryController; + private final DraggablePanelController draggablePanelController; + private final Collection components; + private final GroupManager groupManager; + private final UUID uuid; + private final boolean canEdit; + + MenuFactoryHelper(final SummaryController summaryController, final DraggablePanelController draggablePanelController, + final Collection components) { + this.controller = summaryController.getController(); + this.summaryController = summaryController; + this.draggablePanelController = draggablePanelController; + this.messageBundle = controller.getMessageBundle(); + this.components = components; + this.groupManager = summaryController.getGroupManager(); + this.uuid = UUID.randomUUID(); + canEdit = components.stream().allMatch(AnnotationSummaryComponent::canEdit); + } + + private void setManuallyChanged() { + summaryController.setHasManuallyChanged(); + } + + JMenuItem getCreateGroupMenuItem() { + final JMenuItem createGroupMenuItem = new JMenuItem(messageBundle.getString("viewer.summary.popup.group.label")); + createGroupMenuItem.addActionListener(e -> { + setManuallyChanged(); + groupManager.createGroup(draggablePanelController.getPanel().getSortedList(components.stream() + .map(Component.class::cast).collect(Collectors.toList()), true) + .stream().map(AnnotationSummaryComponent.class::cast).collect(Collectors.toList()), + draggablePanelController.getPanel().getFirstPosForComponents(components.stream() + .map(Component.class::cast).collect(Collectors.toList()))); + }); + return createGroupMenuItem; + } + + JMenu getMoveInMenu() { + final Map> colorGroups = summaryController.getGroupManager().getGroupNames(); + if (colorGroups.isEmpty()) { + return null; + } else { + final JMenu moveInMenu = new JMenu(messageBundle.getString("viewer.summary.popup.group.movein")); + final AnnotationSummaryComponent firstComponent = components.iterator().next(); + final AnnotationSummaryGroup parent = groupManager.getParentOf(firstComponent); + final Map> invalidNames = new HashMap<>(); + final Set allSubnames = new HashSet<>(); + components.forEach(c -> { + if (c instanceof AnnotationSummaryGroup) { + final Set subnames = invalidNames.getOrDefault(c.getColor(), new HashSet<>()); + subnames.addAll(((AnnotationSummaryGroup) c).getAllSubnames()); + allSubnames.addAll(subnames); + invalidNames.put(c.getColor(), subnames); + } + }); + if (summaryController.isSingleDefaultColor()) { + final Set groupNames = components.stream().filter(AnnotationSummaryGroup.class::isInstance).map(AnnotationSummaryGroup.class::cast).map(AnnotationSummaryGroup::getName).collect(Collectors.toSet()); + colorGroups.values().stream().flatMap(Collection::stream).sorted().filter(s -> !groupNames.contains(s) && (parent == null || !parent.getName().equals(s))).forEach(n -> { + final var item = new JMenuItem(n); + item.addActionListener(e -> { + setManuallyChanged(); + components.forEach(c -> groupManager.moveIntoGroup(c, summaryController.getAnnotationNamedColorPanels().get(0).getColorLabel().getColor(), n)); + }); + moveInMenu.add(item); + }); + } else { + colorGroups.forEach((color, names) -> { + if (!color.equals(firstComponent.getColor()) && names.stream().anyMatch(allSubnames::contains)) { + invalidNames.put(color, names); + } + }); + colorGroups.forEach((color, names) -> { + final Set subnames = invalidNames.getOrDefault(color, Collections.emptySet()); + final List groups = new ArrayList<>(names); + groups.sort(Comparator.naturalOrder()); + final JMenu colorMenu = new JMenu(summaryController.getColorLabelFor(color).getLabel()); + groups.forEach(name -> { + if (!subnames.contains(name) && (parent == null || !parent.getName().equals(name))) { + final JMenuItem moveTo = new JMenuItem(name); + moveTo.addActionListener(e -> { + setManuallyChanged(); + components.forEach(c -> groupManager.moveIntoGroup(c, color, name)); + }); + colorMenu.add(moveTo); + } + }); + if (!canEdit && !firstComponent.getColor().equals(color)) { + colorMenu.setEnabled(false); + } + if (colorMenu.getMenuComponentCount() > 0) { + moveInMenu.add(colorMenu); + } + }); + } + return moveInMenu.getMenuComponentCount() > 0 ? moveInMenu : null; + } + } + + JMenuItem getMoveOutMenuItem() { + final JMenuItem moveOutItem = new JMenuItem(messageBundle.getString("viewer.summary.popup.group.moveout")); + moveOutItem.addActionListener(e -> { + setManuallyChanged(); + components.forEach(groupManager::moveOut); + }); + return moveOutItem; + } + + JMenu getMoveToMenu() { + final JMenu moveMenu = new JMenu(messageBundle.getString("viewer.annotation.popup.move.label")); + summaryController.getAnnotationNamedColorPanels().forEach(panel -> { + if (components.stream().noneMatch(c -> c instanceof AnnotationSummaryGroup + && summaryController.getGroupManager().groupExists(panel.getColorLabel().getColor(), ((AnnotationSummaryGroup) c).getName())) + && !panel.getColorLabel().getColor().equals(components.iterator().next().getColor())) { + moveMenu.addSeparator(); + final JMenuItem menuItem = new JMenuItem(panel.getColorLabel().getLabel()); + menuItem.setForeground(Color.BLACK); + final Color color = panel.getColorLabel().getColor(); + menuItem.setBackground(new Color(color.getRed(), color.getGreen(), color.getBlue(), TextMarkupAnnotation.HIGHLIGHT_ALPHA)); + menuItem.setOpaque(true); + moveMenu.add(menuItem); + menuItem.addActionListener(actionEvent -> { + setManuallyChanged(); + components.forEach(c -> c.moveTo(panel.getColorLabel().getColor(), true)); + }); + } + }); + moveMenu.setEnabled(canEdit); + return moveMenu; + } + + JMenuItem getDeleteMenuItem() { + final JMenuItem deleteMenuItem = new JMenuItem(messageBundle.getString("viewer.annotation.popup.delete.label")); + deleteMenuItem.setEnabled(controller.havePermissionToModifyDocument()); + deleteMenuItem.addActionListener(e -> { + setManuallyChanged(); + components.forEach(AnnotationSummaryComponent::delete); + }); + deleteMenuItem.setEnabled(canEdit); + return deleteMenuItem; + } + + JMenuItem getShowTextBlockMenuItem() { + final JMenuItem showHideTextBlockMenuItem = new JCheckBoxMenuItem( + messageBundle.getString("viewer.annotation.popup.showHidTextBlock.label")); + final boolean isVisible = isShowTextBlockVisible(); + showHideTextBlockMenuItem.setSelected(isVisible); + showHideTextBlockMenuItem.addItemListener(e -> { + components.forEach(c -> c.setTextBlockVisibility(!isVisible)); + SwingUtilities.invokeLater(() -> draggablePanelController.getPanel().checkForOverlap(uuid)); + }); + return showHideTextBlockMenuItem; + } + + JMenuItem getShowHeaderMenuItem() { + final JMenuItem showHeaderMenuItem = new JCheckBoxMenuItem(messageBundle.getString("viewer.annotation.popup.showHideHeader.label")); + final boolean isVisible = isHeaderVisible(); + showHeaderMenuItem.setSelected(isVisible); + showHeaderMenuItem.addItemListener(e -> { + components.forEach(c -> c.setHeaderVisibility(!isVisible)); + SwingUtilities.invokeLater(() -> draggablePanelController.getPanel().checkForOverlap(uuid)); + }); + return showHeaderMenuItem; + } + + JMenu getLinkWithMenu() { + if (summaryController.isSingleDefaultColor()) { + return null; + } + final DragAndLinkManager dragAndLinkManager = summaryController.getDragAndLinkManager(); + final JMenu linkMenu = new JMenu(messageBundle.getString("viewer.summary.popup.link.single")); + final AnnotationSummaryComponent component = components.iterator().next(); + final Map neighborLabels = dragAndLinkManager.getSuitableNeighbors(component); + for (final Map.Entry entry : neighborLabels.entrySet()) { + final JMenuItem item = new JMenuItem(MessageFormat.format(messageBundle.getString( + "viewer.summary.popup.link.neighbor.single"), entry.getKey().getLabel())); + item.addActionListener(actionEvent -> dragAndLinkManager.linkWithNeighbor(component, entry.getValue())); + linkMenu.add(item); + } + if (neighborLabels.size() > 1) { + final JMenuItem item = new JMenuItem(messageBundle.getString("viewer.summary.popup.link.neighbor.all")); + item.addActionListener(actionEvent -> neighborLabels.forEach((cl, annotationSummaryComponent) -> + dragAndLinkManager.linkWithNeighbor(component, annotationSummaryComponent))); + linkMenu.add(item); + } + final List colorLabels = summaryController.getDragAndLinkManager().getSuitableSelected(component); + for (final DragDropColorList.ColorLabel colorLabel : colorLabels) { + final JMenuItem item = new JMenuItem(MessageFormat.format(messageBundle.getString( + "viewer.summary.popup.link.selected.single"), colorLabel.getLabel())); + item.addActionListener(actionEvent -> dragAndLinkManager.linkWithSelected(component, colorLabel.getColor())); + linkMenu.add(item); + } + if (colorLabels.size() > 1) { + final JMenuItem item = new JMenuItem(messageBundle.getString("viewer.summary.popup.link.selected.all")); + item.addActionListener(actionEvent -> colorLabels.forEach(cl -> dragAndLinkManager.linkWithSelected(component, cl.getColor()))); + linkMenu.add(item); + } + return linkMenu.getMenuComponentCount() > 0 ? linkMenu : null; + } + + JMenu getLinkAllWithMenu(final int selectedIdx) { + if (summaryController.isSingleDefaultColor()) { + return null; + } + final DragAndLinkManager dragAndLinkManager = summaryController.getDragAndLinkManager(); + final JMenu linkMenu = new JMenu(messageBundle.getString("viewer.summary.popup.link.all")); + final AnnotationSummaryComponent component = new ArrayList<>(components).get(selectedIdx); + final Map neighborLabels = dragAndLinkManager.getSuitableNeighbors(component); + for (final Map.Entry entry : neighborLabels.entrySet()) { + final JMenuItem item = new JMenuItem(MessageFormat.format(messageBundle.getString( + "viewer.summary.popup.link.neighbor.single"), entry.getKey().getLabel())); + item.addActionListener(actionEvent -> dragAndLinkManager.linkWithNeighbor(components, entry.getValue(), selectedIdx)); + linkMenu.add(item); + } + if (neighborLabels.size() > 1) { + final JMenuItem item = new JMenuItem(messageBundle.getString("viewer.summary.popup.link.neighbor.all")); + item.addActionListener(actionEvent -> neighborLabels.forEach((cl, annotationSummaryComponent) -> + dragAndLinkManager.linkWithNeighbor(component, annotationSummaryComponent))); + //TODO linkMenu.add(item); + } + final List colorLabels = summaryController.getDragAndLinkManager().getSuitableSelected(component); + for (final DragDropColorList.ColorLabel colorLabel : colorLabels) { + final JMenuItem item = new JMenuItem(MessageFormat.format(messageBundle.getString( + "viewer.summary.popup.link.selected.single"), colorLabel.getLabel())); + item.addActionListener(actionEvent -> dragAndLinkManager.linkWithSelected(components, colorLabel.getColor(), selectedIdx)); + linkMenu.add(item); + } + if (colorLabels.size() > 1) { + final JMenuItem item = new JMenuItem(messageBundle.getString("viewer.summary.popup.link.selected.all")); + item.addActionListener(actionEvent -> colorLabels.forEach(cl -> dragAndLinkManager.linkWithSelected(component, cl.getColor()))); + //TODO linkMenu.add(item); + } + return components.size() > 1 && linkMenu.getMenuComponentCount() > 0 ? linkMenu : null; + } + + JMenuItem getUnlinkMenuItem() { + final JMenuItem item = new JMenuItem(messageBundle.getString("viewer.summary.popup.unlink")); + item.addActionListener(actionEvent -> components.forEach(c -> summaryController.getDragAndLinkManager().unlinkComponent(c, true))); + return item; + } + + void addCommonMenu(final List list) { + final JMenu moveInMenu = getMoveInMenu(); + if (moveInMenu != null) list.add(moveInMenu); + final JMenu moveMenu = getMoveToMenu(); + if (moveMenu.getMenuComponentCount() > 0) list.add(moveMenu); + final JMenuItem showHideTextBlockMenuItem = getShowTextBlockMenuItem(); + list.add(showHideTextBlockMenuItem); + final JMenuItem showHeaderMenuItem = getShowHeaderMenuItem(); + list.add(showHeaderMenuItem); + final JMenuItem deleteMenuItem = getDeleteMenuItem(); + list.add(deleteMenuItem); + } + + List addLinkMenu(final List list, final AnnotationSummaryGroup parentOf, final boolean linked) { + if (parentOf == null) { + final JMenu linkMenu = getLinkWithMenu(); + if (linkMenu != null) list.add(linkMenu); + final JMenu linkAllMenu = getLinkAllWithMenu(0); + if (linkAllMenu != null) list.add(linkAllMenu); + if (linked) + list.add(getUnlinkMenuItem()); + } + return list; + } + + private boolean isShowTextBlockVisible() { + return components.stream().allMatch(AnnotationSummaryComponent::isShowTextBlockVisible); + } + + private boolean isHeaderVisible() { + return components.stream().allMatch(AnnotationSummaryComponent::isHeaderVisible); + } +} diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/menu/MultiSelectedPopupMenu.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/menu/MultiSelectedPopupMenu.java new file mode 100644 index 000000000..1372d5c84 --- /dev/null +++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/menu/MultiSelectedPopupMenu.java @@ -0,0 +1,92 @@ +package org.icepdf.ri.common.views.annotations.summary.menu; + +import org.icepdf.ri.common.views.annotations.summary.AnnotationSummaryComponent; +import org.icepdf.ri.common.views.annotations.summary.colorpanel.DraggablePanelController; +import org.icepdf.ri.common.views.annotations.summary.mainpanel.SummaryController; + +import javax.swing.*; +import java.awt.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +/** + * PopupMenu when multiple elements are selected + */ +public class MultiSelectedPopupMenu extends JPopupMenu { + + private final List components; + + private final AnnotationSummaryComponent clicked; + private final SummaryController summaryController; + private final DraggablePanelController draggablePanelController; + + public MultiSelectedPopupMenu(final Component clicked, final Collection components, + final SummaryController summaryController, final DraggablePanelController draggablePanelController) { + this.components = new ArrayList<>(components); + this.summaryController = summaryController; + this.draggablePanelController = draggablePanelController; + this.clicked = (AnnotationSummaryComponent) clicked; + buildGui(); + } + + private boolean isShowTextBlockVisible() { + return components.stream().allMatch(AnnotationSummaryComponent::isShowTextBlockVisible); + } + + public void buildGui() { + final MenuFactoryHelper helper = new MenuFactoryHelper(summaryController, draggablePanelController, components); + final boolean allSameParent = components.stream().allMatch(c -> ((Component) c).getParent() == ((Component) components.get(0)).getParent()); + //Dont show moveout if not all components are in the same group + if (components.stream().anyMatch(c -> summaryController.getGroupManager().getParentOf(c) != null) && allSameParent) { + final JMenuItem moveOutItem = helper.getMoveOutMenuItem(); + addUnselectListener(moveOutItem); + add(moveOutItem); + } + final JMenu moveInMenu = helper.getMoveInMenu(); + if (moveInMenu != null && allSameParent) { + addUnselectListener(moveInMenu); + add(moveInMenu); + } + final JMenu moveMenu = helper.getMoveToMenu(); + addUnselectListener(moveMenu); + if (moveMenu.getMenuComponentCount() > 0) add(moveMenu); + final JMenuItem showHideTextBlockMenuItem = helper.getShowTextBlockMenuItem(); + add(showHideTextBlockMenuItem); + final JMenuItem showHeaderMenuItem = helper.getShowHeaderMenuItem(); + add(showHeaderMenuItem); + addSeparator(); + final JMenuItem deleteMenuItem = helper.getDeleteMenuItem(); + addUnselectListener(deleteMenuItem); + add(deleteMenuItem); + addSeparator(); + // Dont show create group if components are in different groups or not in root + if (allSameParent && summaryController.getGroupManager().getParentOf(components.get(0)) == null) { + final JMenuItem createGroupMenuItem = helper.getCreateGroupMenuItem(); + addUnselectListener(createGroupMenuItem); + add(createGroupMenuItem); + final JMenu linkMenu = helper.getLinkWithMenu(); + if (linkMenu != null) { + addUnselectListener(linkMenu); + add(linkMenu); + } + final JMenu linkAllMenu = helper.getLinkAllWithMenu(components.indexOf(clicked)); + if (linkAllMenu != null) { + addUnselectListener(linkAllMenu); + add(linkAllMenu); + } + if (summaryController.getDragAndLinkManager().isLinked(clicked)) { + add(helper.getUnlinkMenuItem()); + } + } + } + + private void addUnselectListener(final Component menuItem) { + if (menuItem instanceof JMenu) { + Arrays.stream(((JMenu) menuItem).getMenuComponents()).forEach(this::addUnselectListener); + } else if (menuItem instanceof JMenuItem) { + ((JMenuItem) menuItem).addActionListener(actionEvent -> draggablePanelController.clearSelectedComponents()); + } + } +} diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/widgets/DragDropColorList.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/widgets/DragDropColorList.java index 635675390..2efb75c97 100644 --- a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/widgets/DragDropColorList.java +++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/widgets/DragDropColorList.java @@ -139,10 +139,9 @@ public void removeSelectedNamedColor() { public static ArrayList retrieveColorLabels() { String currentColorLabels = ViewerPropertiesManager.getInstance().getPreferences().get( PROPERTY_ANNOTATION_RECENT_COLOR_LABEL, ""); - ArrayList colorLabels = null; + ArrayList colorLabels = new ArrayList<>(); try { StringTokenizer toker = new StringTokenizer(currentColorLabels, ViewerPropertiesManager.PROPERTY_TOKEN_SEPARATOR); - colorLabels = new ArrayList<>(); while (toker.hasMoreTokens()) { int rgb = Integer.parseInt(toker.nextToken()); String label = toker.nextToken(); @@ -151,6 +150,7 @@ public static ArrayList retrieveColorLabels() { } catch (NumberFormatException e) { ViewerPropertiesManager.getInstance().getPreferences().put(PROPERTY_ANNOTATION_RECENT_COLOR_LABEL, ""); } + return colorLabels; } @@ -172,7 +172,7 @@ public static class ColorLabel implements Serializable { private final Color color; private final String label; - ColorLabel(Color color, String label) { + public ColorLabel(Color color, String label) { this.color = color; this.label = label; } diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/util/ViewerPropertiesManager.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/util/ViewerPropertiesManager.java index c2ade5ca8..0e4cdb579 100644 --- a/viewer/viewer-awt/src/main/java/org/icepdf/ri/util/ViewerPropertiesManager.java +++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/util/ViewerPropertiesManager.java @@ -289,6 +289,8 @@ public final class ViewerPropertiesManager { // annotation summary panel font size and name. public static final String PROPERTY_ANNOTATION_SUMMARY_FONT_NAME = "application.viewer.annotation.summary.font.name"; public static final String PROPERTY_ANNOTATION_SUMMARY_FONT_SIZE = "application.viewer.annotation.summary.font.size"; + public static final String PROPERTY_ANNOTATION_SUMMARY_EXPORT_FILE = "application.viewer.annotation.summary.export.file"; + public static final String PROPERTY_ANNOTATION_SUMMARY_IMPORT_FILE = "application.viewer.annotation.summary.import.file"; // stored state of last used public/private annotation flag. public static final String PROPERTY_ANNOTATION_LAST_USED_PUBLIC_FLAG = "application.viewer.annotation.public.flag"; diff --git a/viewer/viewer-awt/src/main/resources/org/icepdf/ri/images/link.png b/viewer/viewer-awt/src/main/resources/org/icepdf/ri/images/link.png new file mode 100644 index 000000000..69b5d97d7 Binary files /dev/null and b/viewer/viewer-awt/src/main/resources/org/icepdf/ri/images/link.png differ diff --git a/viewer/viewer-awt/src/main/resources/org/icepdf/ri/images/link1.png b/viewer/viewer-awt/src/main/resources/org/icepdf/ri/images/link1.png new file mode 100644 index 000000000..d79edfebd Binary files /dev/null and b/viewer/viewer-awt/src/main/resources/org/icepdf/ri/images/link1.png differ diff --git a/viewer/viewer-awt/src/main/resources/org/icepdf/ri/images/link2.png b/viewer/viewer-awt/src/main/resources/org/icepdf/ri/images/link2.png new file mode 100644 index 000000000..0e4afc0a9 Binary files /dev/null and b/viewer/viewer-awt/src/main/resources/org/icepdf/ri/images/link2.png differ diff --git a/viewer/viewer-awt/src/main/resources/org/icepdf/ri/images/unlink.png b/viewer/viewer-awt/src/main/resources/org/icepdf/ri/images/unlink.png new file mode 100644 index 000000000..11ae02670 Binary files /dev/null and b/viewer/viewer-awt/src/main/resources/org/icepdf/ri/images/unlink.png differ diff --git a/viewer/viewer-awt/src/main/resources/org/icepdf/ri/images/unlink1.png b/viewer/viewer-awt/src/main/resources/org/icepdf/ri/images/unlink1.png new file mode 100644 index 000000000..c0bae3e32 Binary files /dev/null and b/viewer/viewer-awt/src/main/resources/org/icepdf/ri/images/unlink1.png differ diff --git a/viewer/viewer-awt/src/main/resources/org/icepdf/ri/images/unlink2.png b/viewer/viewer-awt/src/main/resources/org/icepdf/ri/images/unlink2.png new file mode 100644 index 000000000..cfdb4ee9c Binary files /dev/null and b/viewer/viewer-awt/src/main/resources/org/icepdf/ri/images/unlink2.png differ diff --git a/viewer/viewer-awt/src/main/resources/org/icepdf/ri/resources/MessageBundle.properties b/viewer/viewer-awt/src/main/resources/org/icepdf/ri/resources/MessageBundle.properties index 1f0e04b77..eabc9a4c7 100644 --- a/viewer/viewer-awt/src/main/resources/org/icepdf/ri/resources/MessageBundle.properties +++ b/viewer/viewer-awt/src/main/resources/org/icepdf/ri/resources/MessageBundle.properties @@ -267,6 +267,12 @@ viewer.menu.help.about.label=About ICEpdf viewer... viewer.annotationSummary.font.label=Font: viewer.annotationSummary.fontSize.label=Font Size: viewer.annotationSummary.fontName.label=Font Name: +viewer.annotationSummary.header.button.hide=Hide all headers +viewer.annotationSummary.header.button.show=Show all headers +viewer.annotationSummary.compact.button=Compact +viewer.annotationSummary.save.button=Save +viewer.annotationSummary.export.button=Export... +viewer.annotationSummary.import.button=Import... ## Document properties dialog. viewer.dialog.documentProperties.tab.title=Document Properties viewer.dialog.documentProperties.tab.description=Description @@ -851,13 +857,51 @@ viewer.annotation.popup.status.rejected.msg=Rejected set by {0} viewer.annotation.popup.properties.label=Properties viewer.annotation.popup.destinations.label=Destinations... viewer.annotation.popup.showHidTextBlock.label=Show Text Block +viewer.annotation.popup.showHideHeader.label=Show Header viewer.annotation.popup.addAnnotation.label=Annotations... viewer.annotation.popup.addAnnotation.freeText.label=Add Free Text viewer.annotation.popup.addAnnotation.hightlight.label=Add Highlight viewer.annotation.popup.addAnnotation.underline.label=Add Underline viewer.annotation.popup.addAnnotation.strikeout.label=Add Strikeout +viewer.annotation.popup.move.label=Move to viewer.annotation.popup.color.change.label=Change color... viewer.annotation.popup.text.extract.label=Copy highlighted text + +# Summary +viewer.summary.popup.group.label=Create group +viewer.summary.popup.group.rename=Rename group +viewer.summary.popup.group.disband=Disband group +viewer.summary.popup.group.moveout=Move out of group +viewer.summary.popup.group.movein=Move into group... +viewer.summary.popup.link.single=Link with... +viewer.summary.popup.link.all=Link all selected with... +viewer.summary.popup.link.neighbor.single=Neighbor in {0} +viewer.summary.popup.link.neighbor.all=All neighbors +viewer.summary.popup.link.selected.single=All selected in {0} +viewer.summary.popup.link.selected.all=All selected +viewer.summary.popup.unlink=Unlink +viewer.summary.creategroup.dialog.label=Enter the group name +viewer.summary.creategroup.dialog.title=Create group +viewer.summary.creategroup.duplicate.label=A group already exists with this name +viewer.summary.creategroup.duplicate.title=Duplicate group name +viewer.summary.creategroup.dialog.error.label=Please enter a unique name +viewer.summary.creategroup.dialog.error.title=Duplicate or empty name +viewer.summary.export.noexporter.label=No exporter implementation available +viewer.summary.export.noexporter.title=Exportation failure +viewer.summary.export.success.label=Summary exported successfully +viewer.summary.export.success.title=Exportation successful +viewer.summary.export.failure.label=Error while exporting : {0} +viewer.summary.export.failure.title=Exportation failure +viewer.summary.import.success.label=Summary imported successfully +viewer.summary.import.success.title=Importation successful +viewer.summary.import.failure.label=Error while importing : {0}\n\ + Delete format file? +viewer.summary.import.failure.title=Importation failure +viewer.summary.import.different.label=Error while importing : The pdf annotations have changed\n\ + Delete format file? +viewer.summary.dialog.saveOnClose.noUpdates.title=ICEpdf Viewer RI +viewer.summary.dialog.saveOnClose.noUpdates.msg=Do you want to save summary changes of {0}? + ## Signature component viewer.annotation.signature.menu.validateSignature.label=Validate Signature viewer.annotation.signature.menu.showCertificates.label=Show Certificate Properties diff --git a/viewer/viewer-awt/src/main/resources/org/icepdf/ri/resources/MessageBundle_de.properties b/viewer/viewer-awt/src/main/resources/org/icepdf/ri/resources/MessageBundle_de.properties index 50d610a8b..aa8879553 100644 --- a/viewer/viewer-awt/src/main/resources/org/icepdf/ri/resources/MessageBundle_de.properties +++ b/viewer/viewer-awt/src/main/resources/org/icepdf/ri/resources/MessageBundle_de.properties @@ -327,6 +327,16 @@ viewer.popup.annotation.color.standard.label=Standard-Farben viewer.popup.annotation.color.lastused.label=Zuletzte benutzte Farben viewer.popup.annotation.color.morecolors.label=Mehr Farben... viewer.popup.annotation.color.preferences.label=Einstellungen... +## annotation summary window +viewer.annotationSummary.font.label=Schriftart: +viewer.annotationSummary.fontSize.label=Schriftgr\u00F6\u00DFe: +viewer.annotationSummary.fontName.label=Name der Schriftart: +viewer.annotationSummary.header.button.hide=Alle Kopfzeilen verdecken +viewer.annotationSummary.header.button.show=Alle Kopfzeilen anzeigen +viewer.annotationSummary.compact.button=Verdichten +viewer.annotationSummary.save.button=Speichern +viewer.annotationSummary.export.button=Exportieren... +viewer.annotationSummary.import.button=Importieren... ## General error dialog viewer.dialog.error.exception.title=ICEpdf - Ausnahme viewer.dialog.error.exception.msg=\ @@ -962,6 +972,39 @@ viewer.utilityPane.signatures.cert.dialog.info.sha1.value={0} viewer.utilityPane.signatures.verify.initializingMessage.label=Prüfe {0} von {1} Signaturen viewer.utilityPane.signatures.verify.completeMessage.label=Prüfprozess abgeschlossen viewer.utilityPane.signatures.verify.validating.label=Signatur prüfen... +viewer.annotation.popup.move.label=Weiter zu +# Summary +viewer.summary.popup.group.label=Gruppe erstellen +viewer.summary.popup.group.rename=Gruppe umbenennen +viewer.summary.popup.group.disband=Gruppe aufl\u00F6sen +viewer.summary.popup.group.moveout=Verlassen der Gruppe +viewer.summary.popup.group.movein=In Gruppe einf\u00FCgen... +viewer.summary.popup.link.single=Verkn\u00FCpfen mit... +viewer.summary.popup.link.all=Verkn\u00FCpfen Sie alle ausgew\u00E4hlten mit... +viewer.summary.popup.link.neighbor.single=Nachbar in {0} +viewer.summary.popup.link.neighbor.all=Alle Nachbarn +viewer.summary.popup.link.selected.single=Alle ausgew\u00E4hlten in {0} +viewer.summary.popup.link.selected.all=Alle ausgew\u00E4hlten +viewer.summary.popup.unlink=Verkn\u00FCpfung aufheben +viewer.summary.creategroup.dialog.label=Geben Sie den Gruppennamen ein +viewer.summary.creategroup.dialog.title=Gruppe erstellen +viewer.summary.creategroup.duplicate.label=Eine Gruppe mit diesem Namen existiert bereits +viewer.summary.creategroup.duplicate.title=Doppelter Gruppenname +viewer.summary.creategroup.dialog.error.label=Bitte geben Sie einen eindeutigen Namen ein +viewer.summary.creategroup.dialog.error.title=Doppelter oder leerer Name +viewer.summary.export.success.label=Zusammenfassung erfolgreich exportiert +viewer.summary.export.success.title=Erfolgreiches Exportieren +viewer.summary.export.failure.label=Fehler beim Exportieren : {0} +viewer.summary.export.failure.title=Fehler beim Exportieren +viewer.summary.import.success.label=Zusammenfassung erfolgreich importiert +viewer.summary.import.success.title=Erfolgreiches Importieren +viewer.summary.import.failure.label=Fehler beim Importieren: {0}\n\ + Formatierungsdatei l\u00F6schen? +viewer.summary.import.failure.title=Import-Fehler +viewer.summary.import.different.label=Fehler beim Importieren : Die pdf-Anmerkungen haben sich ge\u00E4ndert\n\ + Formatierungsdatei l\u00F6schen? +viewer.summary.dialog.saveOnClose.noUpdates.title=ICEpdf Viewer RI +viewer.summary.dialog.saveOnClose.noUpdates.msg=M\u00F6chten Sie die \u00C4nderungen am Index {0} speichern? ## Signature component viewer.annotation.signature.menu.validateSignature.label=Signatur prüfen viewer.annotation.signature.menu.showCertificates.label=Zertifikatseigenschaften anzeigen diff --git a/viewer/viewer-awt/src/main/resources/org/icepdf/ri/resources/MessageBundle_fr.properties b/viewer/viewer-awt/src/main/resources/org/icepdf/ri/resources/MessageBundle_fr.properties index e848d1242..2830553cf 100644 --- a/viewer/viewer-awt/src/main/resources/org/icepdf/ri/resources/MessageBundle_fr.properties +++ b/viewer/viewer-awt/src/main/resources/org/icepdf/ri/resources/MessageBundle_fr.properties @@ -223,6 +223,16 @@ viewer.menu.window.annotationPreview.label=R\u00E9sum\u00E9 des annotations viewer.menu.help.label=Aide viewer.menu.help.mnemonic=A viewer.menu.help.about.label=\u00C0 propos de ICEpdf viewer... +## annotation summary window +viewer.annotationSummary.font.label=Police: +viewer.annotationSummary.fontSize.label=Taille de la police: +viewer.annotationSummary.fontName.label=Nom de la police: +viewer.annotationSummary.header.button.hide=Cacher tous les en-t\u00EAtes +viewer.annotationSummary.header.button.show=Afficher tous les en-t\u00EAtes +viewer.annotationSummary.compact.button=Compacter +viewer.annotationSummary.save.button=Sauvegarder +viewer.annotationSummary.export.button=Exporter... +viewer.annotationSummary.import.button=Importer... ## Document properties dialog. viewer.dialog.documentProperties.tab.title=Propri\u00E9t\u00E9s du document viewer.dialog.documentProperties.tab.description=Description @@ -925,6 +935,39 @@ viewer.utilityPane.signatures.cert.dialog.info.sha1.value={0} viewer.utilityPane.signatures.verify.initializingMessage.label=Validation {0} de {1} signatures viewer.utilityPane.signatures.verify.completeMessage.label=Processus de validation termin\u00E9 viewer.utilityPane.signatures.verify.validating.label=Validation de la signature... +viewer.annotation.popup.move.label=Aller \u00E0 +# Summary +viewer.summary.popup.group.label=Cr\u00E9er un groupe +viewer.summary.popup.group.rename=Renommer le groupe +viewer.summary.popup.group.disband=D\u00E9manteler le groupe +viewer.summary.popup.group.moveout=Quitter le groupe +viewer.summary.popup.group.movein=Mettre dans le groupe... +viewer.summary.popup.link.single=Lier avec... +viewer.summary.popup.link.all=Lier tous les s\u00E9lectionn\u00E9s avec... +viewer.summary.popup.link.neighbor.single=Voisin dans {0} +viewer.summary.popup.link.neighbor.all=Tous les voisins +viewer.summary.popup.link.selected.single=Tous les s\u00E9lectionn\u00E9s dans {0} +viewer.summary.popup.link.selected.all=Tous les s\u00E9lectionn\u00E9s +viewer.summary.popup.unlink=Supprimer le lien +viewer.summary.creategroup.dialog.label=Entrez le nom du groupe +viewer.summary.creategroup.dialog.title=Cr\u00E9er un groupe +viewer.summary.creategroup.duplicate.label=Un groupe existe d\u00E9j\u00E0 sous ce nom +viewer.summary.creategroup.duplicate.title=Nom de groupe en double +viewer.summary.creategroup.dialog.error.label=Veuillez entrer un nom unique +viewer.summary.creategroup.dialog.error.title=Nom en double ou vide +viewer.summary.export.success.label=R\u00E9sum\u00E9 export\u00E9 avec succ\u00E8s +viewer.summary.export.success.title=Exportation r\u00E9ussie +viewer.summary.export.failure.label=Erreur lors de l'exportation : {0} +viewer.summary.export.failure.title=\u00C9chec de l'exportation +viewer.summary.import.success.label=R\u00E9sum\u00E9 import\u00E9 avec succ\u00E8s +viewer.summary.import.success.title=Importation r\u00E9ussie +viewer.summary.import.failure.label=Erreur lors de l'importation: {0}\n\ + Supprimer le fichier de formatage? +viewer.summary.import.failure.title=\u00C9chec de l'importation +viewer.summary.import.different.label=Erreur lors de l'importation : les annotations du pdf ont chang\u00E9\n\ + Supprimer le fichier de formatage? +viewer.summary.dialog.saveOnClose.noUpdates.title=ICEpdf Viewer RI +viewer.summary.dialog.saveOnClose.noUpdates.msg=Voulez-vous sauver les changements \u00E0 l'index de {0}? ## Signature component viewer.annotation.signature.menu.validateSignature.label=Valider la signature viewer.annotation.signature.menu.showCertificates.label=Afficher les propri\u00E9t\u00E9s du certificat