From ac6154c5eeb375b0337cb50d8991dd511c6a0cb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guillaume=20T=C3=A2che?= Date: Tue, 25 Jul 2023 12:45:33 +0200 Subject: [PATCH 1/8] GH-272 Improves annotation summary --- .../java/org/icepdf/core/pobjects/PDate.java | 5 + .../org/icepdf/ri/common/SwingController.java | 2 +- .../icepdf/ri/common/views/Controller.java | 1 + .../common/views/DocumentViewController.java | 6 + .../views/DocumentViewControllerImpl.java | 5 + .../annotations/PopupAnnotationComponent.java | 21 +- .../summary/AnnotationSummaryBox.java | 303 +++- .../summary/AnnotationSummaryComponent.java | 155 ++ .../summary/AnnotationSummaryFrame.java | 53 +- .../summary/AnnotationSummaryGroup.java | 312 ++++ .../summary/AnnotationSummaryPanel.java | 471 ------ .../annotations/summary/ColorLabelPanel.java | 124 -- .../summary/DraggableAnnotationPanel.java | 328 ----- .../summary/MoveableComponentsPanel.java | 95 ++ .../annotations/summary/SummaryPopupMenu.java | 94 -- .../summary/colorpanel/ColorLabelPanel.java | 179 +++ .../colorpanel/ColorPanelController.java | 47 + .../colorpanel/DraggableAnnotationPanel.java | 409 ++++++ .../colorpanel/DraggablePanelController.java | 489 +++++++ .../mainpanel/AnnotationSummaryPanel.java | 558 ++++++++ .../summary/mainpanel/DragAndLinkManager.java | 557 ++++++++ .../summary/mainpanel/GroupManager.java | 340 +++++ .../mainpanel/ImportExportHandler.java | 53 + .../mainpanel/NoOpImportExportHandler.java | 39 + .../summary/mainpanel/SummaryController.java | 1264 +++++++++++++++++ .../summary/menu/BoxMenuFactory.java | 83 ++ .../summary/menu/GroupMenuFactory.java | 56 + .../summary/menu/MenuFactoryHelper.java | 277 ++++ .../summary/menu/MultiSelectedPopupMenu.java | 92 ++ .../ri/util/ViewerPropertiesManager.java | 2 + .../resources/org/icepdf/ri/images/link.png | Bin 0 -> 7002 bytes .../resources/org/icepdf/ri/images/link1.png | Bin 0 -> 3855 bytes .../resources/org/icepdf/ri/images/link2.png | Bin 0 -> 3965 bytes .../resources/org/icepdf/ri/images/unlink.png | Bin 0 -> 2428 bytes .../org/icepdf/ri/images/unlink1.png | Bin 0 -> 1428 bytes .../org/icepdf/ri/images/unlink2.png | Bin 0 -> 1487 bytes .../ri/resources/MessageBundle.properties | 42 + 37 files changed, 5388 insertions(+), 1074 deletions(-) create mode 100644 viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/AnnotationSummaryComponent.java create mode 100644 viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/AnnotationSummaryGroup.java delete mode 100644 viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/AnnotationSummaryPanel.java delete mode 100644 viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/ColorLabelPanel.java delete mode 100644 viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/DraggableAnnotationPanel.java create mode 100644 viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/MoveableComponentsPanel.java delete mode 100644 viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/SummaryPopupMenu.java create mode 100644 viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/colorpanel/ColorLabelPanel.java create mode 100644 viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/colorpanel/ColorPanelController.java create mode 100644 viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/colorpanel/DraggableAnnotationPanel.java create mode 100644 viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/colorpanel/DraggablePanelController.java create mode 100644 viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/mainpanel/AnnotationSummaryPanel.java create mode 100644 viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/mainpanel/DragAndLinkManager.java create mode 100644 viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/mainpanel/GroupManager.java create mode 100644 viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/mainpanel/ImportExportHandler.java create mode 100644 viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/mainpanel/NoOpImportExportHandler.java create mode 100644 viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/mainpanel/SummaryController.java create mode 100644 viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/menu/BoxMenuFactory.java create mode 100644 viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/menu/GroupMenuFactory.java create mode 100644 viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/menu/MenuFactoryHelper.java create mode 100644 viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/menu/MultiSelectedPopupMenu.java create mode 100644 viewer/viewer-awt/src/main/resources/org/icepdf/ri/images/link.png create mode 100644 viewer/viewer-awt/src/main/resources/org/icepdf/ri/images/link1.png create mode 100644 viewer/viewer-awt/src/main/resources/org/icepdf/ri/images/link2.png create mode 100644 viewer/viewer-awt/src/main/resources/org/icepdf/ri/images/unlink.png create mode 100644 viewer/viewer-awt/src/main/resources/org/icepdf/ri/images/unlink1.png create mode 100644 viewer/viewer-awt/src/main/resources/org/icepdf/ri/images/unlink2.png 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 566e175c6..69f4ff50d 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 @@ -5687,7 +5687,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/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 ee1a57749..7767bf481 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 @@ -22,6 +22,7 @@ import javax.swing.*; import java.awt.*; import java.awt.event.KeyListener; +import java.beans.PropertyChangeSupport; /** @@ -230,4 +231,9 @@ public interface DocumentViewController { void firePropertyChange(String event, int oldValue, int newValue); void firePropertyChange(String event, Object oldValue, Object newValue); + + /** + * @return The property change support for this controller + */ + PropertyChangeSupport getPropertyChangeSupport(); } 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 2f0a65f65..5b03a5167 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 @@ -1298,6 +1298,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 9aa484bb1..1f06046fe 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 @@ -86,6 +86,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 +78,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 +171,88 @@ 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(); } } + public Color getColor() { + if (getAnnotationComponent() != null && getAnnotationComponent().getAnnotation() != null) { + return getAnnotationComponent().getAnnotation().getColor(); + } else { + return popupBackgroundColor; + } + } + + @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 +260,119 @@ 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 int size) { + super.setFontSize(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..902ff23a0 --- /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(int 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..1a94e92e1 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()) { + 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..5d163b314 --- /dev/null +++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/AnnotationSummaryGroup.java @@ -0,0 +1,312 @@ +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.List; +import java.util.*; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +/** + * 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 = name; + this.summaryController = summaryController; + this.id = UUID.randomUUID(); + setRequestFocusEnabled(true); + setFocusable(true); + getColor(); + refreshBorder(); + refreshComponents(); + } + + public Color getColor() { + 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 int 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..cdb23abc5 --- /dev/null +++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/colorpanel/ColorLabelPanel.java @@ -0,0 +1,179 @@ +/* + * 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; + +/** + * + */ +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 = colorLabel; + this.summaryController = summaryController; + + // setup the gui + setLayout(new BorderLayout()); + if (colorLabel != null) { + 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) { + 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) { + parent.addComponent(popupAnnotationComponent); + } else { + draggableAnnotationPanel.add(popupAnnotationComponent, y); + draggableAnnotationPanel.revalidate(); + draggableAnnotationPanel.repaint(); + } + popupAnnotationComponent.fireComponentMoved(false, false, UUID.randomUUID()); + return popupAnnotationComponent; + } else return null; + } 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 addAnnotation(markupAnnotation, -1); + } else { + return null; + } + } + + 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..3396a91ba --- /dev/null +++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/colorpanel/ColorPanelController.java @@ -0,0 +1,47 @@ +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 Object oldValue = evt.getOldValue(); + 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((int) 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..0289df113 --- /dev/null +++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/colorpanel/DraggableAnnotationPanel.java @@ -0,0 +1,409 @@ +/* + * 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.List; +import java.util.*; +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(c -> c instanceof AnnotationSummaryComponent) + .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, pos); + } else { + super.add(c); + } + } 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 ? getPositionFor(sorted.get(0)) : -1; + } + + 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[0], Arrays.copyOfRange(comps, 1, comps.length)); + } + } + + private static void checkForOverlap(final UUID uuid, final Component refComp, final Component[] components) { + if (components.length > 0) { + final Component curComp = components[0]; + 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); + } + if (components.length > 1) { + checkForOverlap(uuid, curComp, Arrays.copyOfRange(components, 1, components.length)); + } + } + + } + + 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..13bf6e04b --- /dev/null +++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/colorpanel/DraggablePanelController.java @@ -0,0 +1,489 @@ +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.List; +import java.util.*; +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); + } + } + + public void moveComponent(final Component dragComponent, final boolean snap) { + if (dragComponent != null) { + final int padding = 10; + panel.sortComponents(); + final Component[] comps = panel.getComponents(); + final int draggedIndex = findComponentAt(comps, dragComponent); + final 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().contains(dragBounds) && comp instanceof AnnotationSummaryGroup) { + summaryController.getGroupManager().moveIntoGroup((AnnotationSummaryComponent) dragComponent, + ((AnnotationSummaryGroup) comp).getColor(), comp.getName()); + return; + } else 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, snap); + } else { + comp.setLocation(dragBounds.x, dragComponent.getY() + dragComponent.getHeight() + padding); + moveComponent(comp, snap); + } + } + } + + if (draggedIndex == 0) { + // make sure the component y > padding + if (dragComponent.getY() < padding) { + dragComponent.setLocation(dragComponent.getX(), padding); + moveComponent(dragComponent, snap); + } + } + if (draggedIndex >= 1) { + comp = comps[draggedIndex - 1]; + final int offset = dragComponent.getY() - (comp.getY() + comp.getHeight()); + if (offset < padding) { + dragComponent.setLocation(dragComponent.getX(), dragComponent.getY() + (padding - offset)); + moveComponent(dragComponent, snap); + } + } + panel.revalidate(); + ((AnnotationSummaryComponent) dragComponent).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) { + final AnnotationSummaryComponent deepest = getDeepestComponentAt(child, newP); + return deepest == null ? (AnnotationSummaryComponent) child : deepest; + } else return (AnnotationSummaryComponent) child; + } 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)) { + list.add((AnnotationSummaryComponent) child); + final Point newP = SwingUtilities.convertPoint(c, p, child); + return getAllComponentsAt(child, newP, list); + } else return list; + } else return list; + } + + public AnnotationSummaryComponent findComponentFor(final Predicate filter) { + final List components = Arrays.stream(panel.getComponents()) + .map(c -> (AnnotationSummaryComponent) c).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..36ffd6f91 --- /dev/null +++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/mainpanel/AnnotationSummaryPanel.java @@ -0,0 +1,558 @@ +/* + * 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 com.twelvemonkeys.io.FileSeekableStream; +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.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.nio.file.Files; +import java.util.List; +import java.util.Timer; +import java.util.*; +import java.util.stream.Collectors; + +import static java.util.Objects.requireNonNull; + +public class AnnotationSummaryPanel extends JPanel { + 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 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 -> { + 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<>(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); + 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 != null && !colorLabels.isEmpty() ? 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 (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 SummaryController.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 SummaryController.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 SummaryController.LinkLabel l) { + annotationsPanel.remove(l); + } + + private int computeX(final Component c, final SummaryController.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 SummaryController.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) { + ViewerPropertiesManager.getInstance().set(ViewerPropertiesManager.PROPERTY_ANNOTATION_SUMMARY_IMPORT_FILE, + dir + filename); + try { + final File file = new File(dir + filename); + controller.importFormat(new FileSeekableStream(file), true); + } catch (final FileNotFoundException ignored) { + + } + } + } 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(); + ViewerPropertiesManager.getInstance().set(ViewerPropertiesManager.PROPERTY_ANNOTATION_SUMMARY_IMPORT_FILE, + file.getAbsolutePath()); + try { + controller.importFormat(new FileSeekableStream(file), true); + } catch (final FileNotFoundException ignored) { + + } + } + } + } + + 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..d2e225825 --- /dev/null +++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/mainpanel/DragAndLinkManager.java @@ -0,0 +1,557 @@ +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); + 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..62ac0f1ea --- /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) { + 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(c -> c instanceof AnnotationSummaryBox).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) { + groups.put(group, oldParent); + oldParent.addComponent(group); + } else { + final ColorLabelPanel panel = controller.getColorPanelFor(group); + if (panel != null) { + panel.addGroup(group, y); + } + } + components.forEach(c -> { + if (oldParent != null) { + oldParent.removeComponent(c); + } + }); + controller.refreshPanelLayout(); + return group; + } else { + return null; + } + } + + /** + * 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 (!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/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..326f6857d --- /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 ""; + } +} 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..28ce85590 --- /dev/null +++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/mainpanel/SummaryController.java @@ -0,0 +1,1264 @@ +package org.icepdf.ri.common.views.annotations.summary.mainpanel; + +import com.twelvemonkeys.io.FileSeekableStream; +import com.twelvemonkeys.io.SeekableInputStream; +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.images.Images; +import org.icepdf.ri.util.Pair; +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.io.*; +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 IMPORT_EXPORT_HANDLER = new NoOpImportExportHandler(); + + private final Frame frame; + private final Controller controller; + protected final DragAndLinkManager dragManager; + + private final AnnotationSummaryPanel summaryPanel; + + protected ArrayList annotationNamedColorPanels; + private int fontSize; + + private final Map annotationToBox; + private final Map annotationToColorPanel; + + private final GroupManager groupManager; + private final ImportExportHandler ieHandler; + + private final MouseListener mouseListener; + private final ComponentListener componentListener; + private final PropertyChangeListener propertyListener; + + 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.ieHandler = IMPORT_EXPORT_HANDLER; + this.mouseListener = new SummaryMouseListener(); + this.componentListener = new SummaryComponentListener(); + this.propertyListener = new PropertiesListener(); + this.fontSize = new JLabel().getFont().getSize(); + LinkLabel.rescaleAll(192 / fontSize); + summaryPanel.addComponentListener(componentListener); + controller.getDocumentViewController().getPropertyChangeSupport().addPropertyChangeListener(propertyListener); + } + + protected DragAndLinkManager createDragAndLinkManager() { + return new DragAndLinkManager(this); + } + + protected GroupManager createGroupManager() { + return new GroupManager(this); + } + + protected ImportExportHandler getImportExportHandler() { + return 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 { + ieHandler.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 = + ieHandler.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 SeekableInputStream 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(); + ieHandler.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; + } finally { + try { + inputStream.close(); + } catch (final IOException ignored) { + + } + } + } + + /** + * @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) { + if (annotationNamedColorPanels != null) { + for (final ColorLabelPanel colorLabelPanel : annotationNamedColorPanels) { + colorLabelPanel.firePropertyChange(PropertyConstants.ANNOTATION_SUMMARY_BOX_FONT_SIZE_CHANGE, + 0, (int) tmp.getValue()); + } + } + propertiesManager.setInt(ViewerPropertiesManager.PROPERTY_ANNOTATION_SUMMARY_FONT_SIZE, + (int) tmp.getValue()); + setFontSize((int) tmp.getValue()); + } + }); + 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 != null ? colorLabel.getLabel() : null; + } + + /** + * 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 null; + } + + /** + * 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 null; + } + + public Color getColorFor(final String s) { + final DragDropColorList.ColorLabel colorLabel = getColorLabelFor(s); + return colorLabel != null ? colorLabel.getColor() : null; + } + + 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(c -> c instanceof ColorLabelPanel).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 ArrayList colorLabels = DragDropColorList.retrieveColorLabels(); + final int numberOfPanels = colorLabels != null ? colorLabels.size() : 1; + if (annotationNamedColorPanels != null) annotationNamedColorPanels.clear(); + annotationNamedColorPanels = new ArrayList<>(numberOfPanels); + annotationToColorPanel.clear(); + annotationToBox.clear(); + + groupManager.refreshGroups(); + + if (colorLabels != null && !colorLabels.isEmpty()) { + // 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); + } + // check to make sure a label has + } else { + // other wise just one big panel with all the named colors. + final ColorLabelPanel annotationColumnPanel = createColorLabelPanel(frame, null); + annotationNamedColorPanels.add(annotationColumnPanel); + for (int i = 0, max = document.getNumberOfPages(); i < max; i++) { + final List annotations = document.getPageTree().getPage(i).getAnnotations(); + if (annotations != null) { + for (final Annotation annotation : annotations) { + if (annotation instanceof MarkupAnnotation) { + annotationToColorPanel.put(annotation.getPObjectReference(), annotationColumnPanel); + final AnnotationSummaryBox box = annotationColumnPanel.addAnnotation((MarkupAnnotation) annotation); + if (box != null) { + annotationToBox.put(annotation.getPObjectReference(), box); + } + } + } + } + } + } + } + refreshPanelLayout(); + tryImportSummaryFile(); + } + + private void tryImportSummaryFile() { + final SeekableInputStream inputStream = getDefaultSummaryInputStream(); + if (inputStream != null) { + if (importFormat(inputStream, true)) { + deleteSummaryFile(); + } + } + setHasManuallyChanged(false); + } + + public OutputStream getDefaultSummaryOutputStream() { + final File file = getDefaultSummaryFile(); + if (file != null) { + try { + return new FileOutputStream(file); + } catch (final FileNotFoundException e) { + return null; + } + } else { + return null; + } + } + + public SeekableInputStream getDefaultSummaryInputStream() { + final File file = getDefaultSummaryFile(); + if (file != null) { + try { + return new FileSeekableStream(file); + } catch (final FileNotFoundException e) { + return null; + } + } else { + return null; + } + } + + public void deleteSummaryFile() { + final File file = getDefaultSummaryFile(); + if (file != null && file.exists()) { + file.delete(); + } + } + + public File getDefaultSummaryFile() { + if (controller.getDocument() != null) { + 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 new File(folder + '/' + filename + '.' + IMPORT_EXPORT_HANDLER.getFileExtension()); + } else { + return null; + } + } + + /** + * 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 ArrayList colorLabels = DragDropColorList.retrieveColorLabels(); + if (colorLabels != null && !colorLabels.isEmpty() && annotationNamedColorPanels != null) { + for (final ColorLabelPanel annotationColumnPanel : annotationNamedColorPanels) { + if (annotationColumnPanel.getColorLabel().getColor().equals(annot.getColor())) { + return annotationColumnPanel; + } + } + return null; + } else if (annotationNamedColorPanels != null && !annotationNamedColorPanels.isEmpty()) { + return annotationNamedColorPanels.get(0); + } else { + return null; + } + } + } + + /** + * 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) { + 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 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(c -> c instanceof ColorLabelPanel).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(c -> + c instanceof ColorLabelPanel).collect(Collectors.toList()); + return idx >= 0 && idx < components.size() ? ((ColorLabelPanel) components.get(idx)).getColorLabel().getColor() : null; + } + + 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 ArrayList 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) { + final Color color2 = getColorForIdx(xIdx / 2); + final AnnotationSummaryComponent comp2 = getComponentForY(color2, y); + if (comp2 != null) { + 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(); + } + + } 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); + } + } + } + + /** + * Class representing a label with a Link icon + */ + static 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)); + } + } + + + 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..896d48cdb --- /dev/null +++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/menu/MenuFactoryHelper.java @@ -0,0 +1,277 @@ +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; + assert !components.isEmpty(); + 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(c -> (Component) c).collect(Collectors.toList()), true) + .stream().map(c -> (AnnotationSummaryComponent) c).collect(Collectors.toList()), + draggablePanelController.getPanel().getFirstPosForComponents(components.stream() + .map(c -> (Component) c).collect(Collectors.toList()))); + }); + return createGroupMenuItem; + } + + JMenu getMoveInMenu() { + final Map> colorGroups = summaryController.getGroupManager().getGroupNames(); + if (!colorGroups.isEmpty()) { + final JMenu moveInMenu = new JMenu(messageBundle.getString("viewer.summary.popup.group.movein")); + final AnnotationSummaryGroup parent = groupManager.getParentOf(components.iterator().next()); + 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); + } + }); + colorGroups.forEach((color, names) -> { + if (!color.equals(components.iterator().next().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 && !components.iterator().next().getColor().equals(color)) { + colorMenu.setEnabled(false); + } + if (colorMenu.getMenuComponentCount() > 0) { + moveInMenu.add(colorMenu); + } + }); + return moveInMenu.getMenuComponentCount() > 0 ? moveInMenu : null; + } else return 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() { + 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) { + 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/util/ViewerPropertiesManager.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/util/ViewerPropertiesManager.java index 42124ea77..43f5779a7 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 @@ -281,6 +281,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 0000000000000000000000000000000000000000..69b5d97d7d20516c4b8a921c8de827db9e7c0f04 GIT binary patch literal 7002 zcmXY02|SbkAAh#7A=k{Av$;Y=IU;h@+*jm`Y?L#{ButKNC3nh^yA-0sU7;LX6sb^B zXkt|EGIE94|5?BPYp=bw=kxu3KcDyKc%JX)`+ibS*_re4?BxLffX~tb;|Ksy@Gk`5 zf`dP=Z}$6xKX4ywa}2;@f9|$d<$w}yyoGxN0PI1rzYw6PL>v?%E?L@|BEIt?_i&^3 z6ZSv=VDF42#>9#6_1Ep#+z3IE=csMt`f-56{jJ~VVsiCnbJD{(@ ztJxLim$1bAqc;;?oayDc#sM%s-~E#0xhw57)x$Y8a%g6AgLAIke0U*LNAY z+IT;D3ehhMm}3mk$OoxP^ffzTEuyG=B#0-X&wvhP=4d_8QqE|Ap$2uY#Km>!4Y(a^ix zDrT8JLrNyQQ@lY>Uvbyb=6e`es%cAdqBFqE$-bzCN7R2c3(VE>;s}+mK9771Y$;!xIY6JG z+DYE^t}m`{Tkxk!LJT%CcE%qOR4Nq>wcg==XLu;>p&2{7aVAOl*Aqz#74be?<_(b5}+}}Ue#B-OJ zZBi*@*Vxz~ZWg!9%Yd{>vQfCxg*(6G=>-WrK_2#R7dmXq^#D_ul}R);5s8UApRa? zQwc91Bf2m9s2Ok++DbBYs0tRLg8A-+NYExLC6cn1+<=Tmk0+o%gQ&>FgK?~~r%4VE z2&k6sL;WI|+WnI*~s$p{BLTc^v)cT0qv)G4lpa-BplxAxfZ1GEM-NJ$B)RAMf6AL1zsOuri*e(`qZp+GmnSJsET2E#bP=58 z%erS0t{k&i69mu4_3v^IaDAX; z2mqOo1c5dW0gQs;7sw1$vKneJx?_DI_FFVEb9D_2T;N5|Au+x~BayZnU*@r*2w;t> zPU0SqMfL*zkPV0GKW5XDmgFth*Il}S9eiW6$HlUE!;gG$sZaLPtOZTTSX}0(YSCTC zFCcs6TFLLog`AH$bj6HWjrSAFQsn$~0*z+6bG=+Kjb?hGbV zQqGs$qr#{!IUfP+H7^uW^?*pKJt@7d8J!I?6Li@x@-xC$vmcC0E*vQB{=~#6U~Paq zRpK{n34?6J1=kVVibsNmLoNn&O&PMbI*WDP@XD*eE@degJyo@o+QVr zbn7Yp!5<>+MxyN+mK1VXrkItt`*He{@3Y%JWkf($>tNyW&;qUlohVQlYf)iwcsKcq z6Lbtj<|7GcLDR{f+QZO}f_GBAg3m-GNZYb8qo)WbDbkyX8`c<}S6uWEoOgd+`M+|q z5h3M}{mWHJDHd5Hb_KWE->Q;?UsBH0LLUIpe;Hy~SB`LP3S=9dy#M8W7kCqzhZsTP zWHvpPvQmT#J|;$vT3<|%?rTBcUETd0q;NRnRG#=W6Pb182~w9_?_fQy2C3!R-#~o! z2#70XoVCPM%<{tF0Oc8vT+Di%s0isa$+uy7wn9j6JbQs_iu~NDaw;*B(V_lhMv#!l|=^u>WzbbsA*@n@%%GR^OevW zqqRQiKXtj!u&jPeL@FK6BM27YO7|TOS7NBq+z*6bzdVPP*pURPbtbj17dm_ww7e3* zmD3a)`z(E?{OyBT>^)}d%k#qrTmad6qno(}X{9Sc62lL3cSS}iJoST^4gDr)v)O0i zEUiEV8?A4Ct&7nIx?=RVTyZSJfb4B)%4_Lo3bAd7f^*x8(Vmr7Tdq#5=_0yek4|S` zYR5hcmxqEn1s6}|cl4A8RS->Amk#96W(cCXpVtrSc^h~qq=htxEqjR!Qp)~rM4)I@ zE&2F2Nfvi>Ia(&x!7KCl)jEq^X}(3RhW50x)Cu>Me5uojIFW!S$2Tr7ghnq7Q93uH zlP!Nax7oT)p&dCYk(oD}LR@9Bu98f(D?}08b?%yDI)ML(-+f#OUz^`{?J=)<-z5S_-^QhkR|8W@$+TGnDr$t zTykpleBVSeCHEm}-FA`MAT3^dXe-~DrHZgv?ZLNxg*8B0#0kAl7M5-(J<3O?)i=g; z`AQSk%KQY>+~Ih)2O+L5H$MU&wNCcQESkm2F!kZX*mYrjvPzSQir%M1W&Re~H(c5u zDF?P(CrPbcEjvn#JbrEX<=w)~xjz zyG}g6vCzH%=uUeGS27*7;3(OQkneWnw{J`aDyVG)m{!y?A z5OTv-zAf0hT^fKTo@HSwmJGOey0u1n=CSKL3oW%w9No5r@ui=G?hbkx*5BQQ|2hhh zvU00b+RA@KlvV>~HW?FL<%T<^LESzEf>pP>XUPg%`7eml2msaBCzBAsg9&;oVDnO@ zUz>9)pObZrALuR?ldG0y)&vSZRM8}BS3*diY{|R6FO3kuc7&Ms`h%3AOdWdYCmi-M zadf#ZUI-^YsrwqDXm6u7IriiFF|=buCfo}?Z+$0OwDa$pzVivz;A&kv6rU|X1pL4U zXX^E}mIhI(aVJ^3jQDWNbtKr#&Cr!~WD>g#;4=Qwy-&;ecvz==tTbi~uGf`i z#1{DSV1h4jPg>{i-v5o2_SQ2VwVqIi^~@z&pofrt=VhH&45WC2Pa~Mt?dR84GP0Gu`6i_}kd~Q8Ns)>JFY($RC|XSL#4|7|igEP5F9LQ! zm(&2?e0<5x}ooW`tbU_Uu!WW5@g#j2rF5u&5MWE3Bt7gMB(}1wxST+D4kN#xu8hdHTyRav{M{{K=XIhfXXD;W*E{ma!SYm z4{6sOvR_s|7=dm&t_HSeI;?rpS>yY!ECW(5%FX?x7%d+9%Q9Np=y>*Ocmh5|2*WJ}%$BBv)K$PpdD+U7 zIGV`|q50C33aW`O@oyuP9#!H3tlC#(6YidbZ*UsK+9^WWK9(4Wkc%YAtY_{=cq~FG9Z~Xb2!7**Q(? zBdDUGMsaMWNdWj#AW%fQSGiAQ)t-gP#);!Q0=eQt!FqoF_b#$!Q0qUPLY|yj1@OE` zDd2#%ED$#){C{!Wd*MZCV91o^4S4IUld_e4mAzx%@NH%72X_W^xTcUBVkqB0;2?D$ zwSS$*SdCGV@B#1)+bd+wTD4aQ-Tj(u1gMqeUq4<7P>%%bm;TBXvxKw5)$#y1S_GF< zW$6R6Mc>K2l`kRS@l!ZSK*(-9mp?wF2SUx6_7%V$=1eUoZYc|iVYNA931yF2D{?r5 zx6wsd|496tbWiq-}Iw}6Co z4L;o-Erm385D&%}X?)hY^sVO@Fk84(07ZbOwruNp(o#{<{PsvWL(*~vzgi&0_VSre z5wj;D{1rL7sZ-$>iug);J{tqNS3%+B@OsJ`QNHu5q@q?ws_?%4HeQIaQMvChJJwt8 zhPh&c-gwE>P7^OR$lquBNHS!YmK<#FclnDv7LTr{R|Z)thZR>defUE^7CFt?v9+o@ zk^ZiWZNJ9OPM!8~mA2Z$D3JNpbB&sBb8?aG)#@z|@*mr-g!f8PFI+^9>if(`iQQj* zA*|2F+WwPw%=T&Pre~N?Oa}LYj*DQwW8Nmaq~`cea;f<(%CFzh!JQ97ZMCE>=p>ko zoIf#jl5KVGJPENV_TqZXH^thH%V%I|Yg z_S^;^UQBIAR-5y~{h-RlX>R|ZrF|kze3%T5eHbr$Oe)&xkhG(6R(ST zmN`p;Gb+YBZQYPsV@^(e-y9~~QJH8d1^Syu(F;#0Y5Glb>{MJk)~RGv`)BFwB9K|V z*4HxpYF*`bPdjZ5)PK24kZwW?E1jweAG^t|)PzN}5Ymdl+IvIRyUO6BHPM<=YH9{_ zp2uhYq8@ZQx^#kVGuVl%w8mhtd1=|AGK@?$> z{i^p14-qG6F%QFo;w77k)VM!k?pTa6`?|D&cO{@*PFJGDB0L*cxX>f9q5Px zHiM%BQr#f^TE_Lhc4KV!4`m^JkSJQi~N| zB!G2hnRJgjm)IMZQ>Hukw4%F?Byj&Ypo?_l=Vc_EPl`c26d32qR+Q>EOkG3)7P_<$tnPHd$0=co9B)`D|zW*J~H0WyM0bXjuYI5()xqfYZKlM0l*1& z9|`XhsXx-u??DO&siH~)k5hfOf=<*32#nYie2z`sP>s%jBxL{jr%<&Srg`D7Sa(Be z4?tI;w~wno22}16(T-Yo;N#}k^ZUBPNych;`TBRUdHR)N^u26j7&V5w&xodc?Vqc| z=F$w)@m9~nMLTs|YN4GcseAqc_6eY?vS_LX^(L@AlQ`c z$-}#BIGy$%$X-nku82af%mwVJ)W1+KXy}UJbmkoAbABc&t8L}4uI#ShgcK?B03x%; zeKf{L>vh2%5Ms7@0&kr9Z=$86e~bV;!CcqMamB=88e|MpexxcUmghB#_yWeOqD=*O zeEN^8W&h&Q3o9l+v3Rg`#*)$L0vot!x&UPqYA%hROxQNsr|7 zrRY9(qe&EgmJEm`Rcj%~B$g%g`gQIzs;gX9ud5(TE|4Emv6tlj zc%GuN_c4p2HsqQ~UF8}~RYe)c$?*2k{b>=@zY6ur0SEU!g~~!*D*wvQYu-$GMoo7x z_pA~t^woNO2FZ>HJcL?BN*>(ZwBV z8Mfye6WG)uQbi8S@%!!D8xIHEF5Z!glYH)G&n8_{gi~lF;=4hCM+3NQnHveioLg+F z7E=N2pcuESnGd!ylrGeU`LKyd4A06m>-K@&`Ym4NU*Wht8m7d5Dt_El&u(ZWdVvY= z=xG8O&;J+>u|1xcNqrx`(PYIMHJ9)|riG?qE$~L5(qySeR$4ds08-Z9ST8Q@)j~Pxc^5$Fz~qmpo5%KRK8@f9_FkAjtNl<`(x+ zmC5ESaY)mc2)#R}k|gT#A39XB%iYl{PcTy;tbhBOu025zQyda`T4zv zs*pxuS%yAOsuzuD&LX@&ZN${ffOh-Q<)gy>;)Lss15c+at$i};+J6dsil2Rz`KaTr zG^5x-uSrqWw)NVDi@71D?5-!rh0af(eqd|Oua~0{*p*cc^4prc(mEH#Z1v>V|eJ#@~gs@Voh) zYy?TCj!wg+8XcW~4m|APr&m!4 ztq9AsaxPkHZ6kzDgLhdN+(C4}r2?bn0`vTfN!z4fI#&PYH@w!Nd(q?G?PO~w!Ag}& zWZU0$2mQl1(Xgk0Z-)0Fvy zuOY^PxbSOy2{=^iA3kS}nXwI`C=rJ0x{+-RqDJ91vM*W7Ok_)G(pYZMoFOEaB4r6FYmp^m zDQQlN+!#wJ#LcwaVkD!oW~Tpf|Ic~O^PTVgz3=<`zTfwI&UsFntMh(>w5l`!00eug ztvdjK=pzV7;?UtxR8Z2ug(hL@y4HQH6?Wzt&4R(6eZOWEa@vp*ZBIm z<7td}_s>UtKdpvm%})1NeCRs6``7fZfJM=`gOWQF2hRBZg-d~+-m6-Dq+tkzb>CuD zPF#07QhOXr4sK`|oZSpB!lsC&9WmN&UR z6}nqtckHP2CCFjNO>pRj82QY;j}(3xJFKHZf>#)TA!WykukUq(TVPI}5i$&WzBM^4sq8FaR{iv?}-joIF$!$XPzk zPKWa=Y)J6NA5brFPQuYx{TBGF1Zz%ceKOY`1~mAIu*lW29y6kL+e)ti!^_to5Hf&n6;#Z&sGDNR$H;<9o?`WBUCL z1I216uQ*{^L-0X4Z4l&SD}S>(I+y-T06d4dPm|1T_eBFvNIs&Z%%#@I(G7(W^qZFs z>RbaIfk@n*f3-KHnM#Pm_fU|`72%5+QHi5_PHE=IT7{lM$({r-tvKY8}$)&58?9nVFFc%tL zx?p?EKP+P@^nvP9!=?Q?0eD6}nywM@IXY&bI0*E1N>Q>d;}|%GdVnWUu@`XG(f>BL zn@cr>@(%dDotCC#(Q$^#OWW(Ym-gytd^Yy91IbVYCAPT0n&j`~mw=|GKma&*Odt7(j_ zjw`-j_D}>`2NL`p4syJ`#&D$vFL>A{Mg!hHt3)G9_yr;z|9OI1gDHM-t`-vjc|j3y zEL)zPfo!9vPjy{Y9(d@>qSF`bcByRy)g(Drt_`{1o}1nR$3fnl#i!@fi2J~k>froM zb*B2|MGcocc$wUt-4msh6e*nrI+U@q{hHW}n#`g}`F z)7u*V+%R@_YDdqgtEl*xDw3}p?)2Uzj+Qu-`P9BLYVifI0vh<$9LyMNOx1RmuL!=9 z(ryel>W|#%7(}E5Zc(>7Req45KEKw4%xL6R&2>dxSsEwqry8pix8s4W;BkU7qb_K- z@NnU1ZerAVyYXqCOqEjVsK{a-k;KX3O%ePr>(a(Pzx%qL2$a^&KS z)HIAOTl`5(uGkd!;^dFWPJblyYDP}-2R%V#XEmaA;E%3nfii9Wb414Fs4QcnMCS1v z?4!>o&!y9(EgsEY#I_!ac6o^9;R1{{xRbXC$J)Dn(#K-U%l^VP`+aQ7=s^wz6NA+; z@f%bcm>u%=si!aIWS5`oo+Z}~tCjQA=vlMmVx&pr{!I&s_h@t=12Xnz(-(4H0}rP{ zhxLd>7j)eHaF@xABVSKy#9DmKl~g;~F#AoG8&j^eE(Lfr_BOv8&UH$%0 zitkGilS6W_To1K6J)JHEdW!C7<`}%SVlN494Pvb$Xlj~ed5O|;uJz6~;V~Ng^WMo% zsh^kA&rbpszN=$CfpZQdC@*qX^QjEo%V9XfS4=>NnZmfNDTlM){z}g&?$V)e7E-JL zP7-tdo3R5n#ArZC|D2}!T=1NOIaDul(IaCOUjh|*(qv3a0WK{J_~3+HyxhC2A{7dJ zaKy$IVA75OGZjxv#9OyGA#{IQ03_!rr`P}qy~dbQ7pYX#BsJBMZEFg@tY8OMSl?1b zgnWxCwN+ik0U!n2_t>E>Z?Sr@zE-YaPCtj=MNfMEbpi$Le{E#jOuQIA3!6|C@I zf93Y$`jxHl5jpHI;MAYqYk^4(E4;?Am!lc>{!MYz5Y43cq6~wh`!``e zw^UpJ?~OC*-HeDoJ~XrZO<=HJWC~5guO4e48|jHnc`|FrLAk&ac6RkhX;W$4VZV#^q2c~#0SL26UP?){E-PHrqh33qm~we7DA#IvbN+*QkM*mkyMj$}BZdXN=7 zJduRXPHB(LmMYA9Q$e0onEr~MD=QI+-fb%))d)msC7h$wM@y;x~SGP2TPV-_(T9 zdy_;7PbIwc1As>#uVdDl@{D7|fRF31;z_9ytitI z9UhoR=?Z_V$1G{~fA(fsY(^ybx)1lYWyP6J%X$URNejtBxv88YdA3v8hi0dJIp#r+boJF{@}(Xg;`fz^ zRgdm@oUV3p6U&<=2i|$CRpvV>UODnE?;u7S?LY$f`$LCa;PkSHyq@2>SkEw(n*OS8kFCi zWIia^49=^3(nm7YB&?)lU&FfUm{`^31yXN(|YWehuS#r$T+XLLq zj`FHZoJQV<>jNp}MNxNUBa6i9WWoLAT_(u>Nd}E~1!yy6GRC5kMtAD-bUfnTciJCxrCY7L`kJ6WNB|51Q&N=xdlHCRy}`&CLE(P$hU%^zB1c#P)Xt3#Cpp4Grl zKW^F#x`4}C=qbqRwMZ8I-;%D$C`t}fDG>*%S|=yB1|U1FMaiZBwdKhk`rbAFJRA^S zI#}L%eq=2|f~8ZhIG=4WU-PntfJyuz*_xLBW>!!YLvpGfpFV^VvFNs|n|pCy{$(+s zz+mT3fPyFc*2IA9`A~n{YDZv`jN~243-h-v*ccF;-(D1kbH)|lW6WR@CtYIp$}oNx zCSs4PxJHTWRXCVX%nrT4HaJ`0@?ftLgIND8uvzA}d~sQ-8t%}gv$9_>T{kIHTea=G zExrB5V?gZd8=g0JUzb|lGD6Q9!>)H^x!a|H6S%L~W*;FM?!Pf~!3|lY;%(wuZXUKS z6}SkH5nldD-0NeQ7?4m#xovU+(c_0cJ4~ai^lPqs-LPSoJ$U$d1T445MH3(R8M4bcbxp wl>mIS*kb+Ujp%hntcC3}){QcfUBAE;<0n^B?LQZwzpjA2owIH2K6>K+0O5e{2LJ#7 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..0e4afc0a994c9e62e13b70d9292c9a8da622a510 GIT binary patch literal 3965 zcmX|E3p7-17d~gkjBy!6avMyDL`ZI-kW0uVcZv+fB+5vkPnsEtDV6J&h)6;sj7w3` zoS!6;NiLBvC^8fhF&O{R_x=Am>s{}D_TJCl?|%0>d!2Lbh|?jgpsXMO09ZR)D^~yj zVG#r{Xn6BIR~ig&=)l8=tbmQpr=YPU1J($TY<(gDKuBV9fj~~KG_2%5Z|7*uKQ6pQ zNCfvgk>UjaqJnl-7Va^hzUPHJ_VbWQ%#YmJSin~mw9_c_k%$9Yy+E%^i59KQQvAnBLy7Wt=X#VNNC#8@rQ@G^868U z8kR5_11uv*1tHafG%=2LHK=z&MPhQ2GEm&H3qSeC^B{}oGi4=ciYmU>u82VN(XC(ho+8gysU{PuSuwl*K6YbDx@dRJ__!sb$WX<>9 z0}h~Og6ZW5r37wr81QbHpHvZk7KT^fg*~Z-Q8Y3EM^shdX!1(!Y|-oFrEdKl_P}z7%-C^if1JWrA$3+0T_nt ztQFMh@y~(T8n%(BEI3IwP+hP7pWg^ikmHP$mV9eByA}h-DFJU>$3khIVB()6E<7nY zih`Trh&=UU!6~RK^$$~eBhppsfBM&1k2iBxz=sL<-t_fDo&rXyHuF74rykrvfQ!(h zsE;N8iTM$bmCToCz?UOyF65@?;ivYGeOn;LsAU&q>qmYxJ5@0@4E{Mwt+X6vpC@QT z=hkd#^6d0FhLTq2u+W$YtUSo7RWKSU_8@x#<<6|! z07*hoA@gWdz{RU)pi2bDtHUVGDxcnwn4WjPwPujMq7C$el2DvM88;H6(^&ufVByhI zv2-&mfjciSf|B;=%bx{QCoi(4+3F)GTgk;q6H41f?3@ODJ}zbiRm$`u$wNIIC@z(M z*PP~bgdb%y&Y4821eNJtBpy@eNQdj5szMl188^G5L05tGeSD|#_Cd+e2SH(^6I$e{ zU2nc5^y`~i!{2dDyUghYW<;>KRRLn@j%%*Q0z|$ZMRs6v)L6VAE#&_9QXURb+p0=X z2)J)EpaBy3#QX9sDNFsL^$iGC#8t=ar@Y6&CnAkhaALq8^8?+%$Hkbu4+d9*m130x zp0bay%c1QCu}^-LfahPogObGm8g8UMz$|pBfkKoknHCq}-k&b*4~`6%GULC^lGq9v{SghU1LeGSI@5S=2LZBr zDjz+N)$2d77%eLDpxBH|bhR4E`ky^7kk8 z`gbjUZ0FqP7nr`uLPU9G>`nS@g1Am#0hAZC!`LRv&XU=)6z+x1QfkvR^8+P%Rc+E)Dr+ z%&Ek4O%!76O7ir6xFm5Z+qot}lObx!Spp^FG_FQ2?|H=acPF3?3=wLp=)ov{S*v;U zO@LDgaLY!k-?cT(&MhAQ0#7&IQWF*@(Sn&XUVX&X$l*O-31LVHOr~S$EfM7VoqE{) z*6UC1GtcIqe6c5#_WSOFpLA!t`;`2jNaJl<<1IqVy|~3JxVSBz+q0LeBC6Nj)+O!8 zjodpekv?q^^rN`unwXQ`3Z+cMw{X>f!-btDOFf=0_J@Z5v zP1)abpQ{nig~gUCMa!%grQFDix!P-b z6<3o;N2g7@Q%yMGYU{nqzMuEciy#9Fc{WqpiQpP_Zt+9Uyfp#WGqQ8NMTb&uq$V^v zZBWQ76{aOtiW-uSu|@B(4_{Fniy8R+r%U2oB&1Wk;jB+{An+~g2|Y`8HKD%j)iuxU60 zxm`1a!t_i3a8}Ts$3PnQa18?RCZg~h?IA(@BAS6XLbme)ahKGEX6%rcFwO`rwXLGXV>5sX2n<4h3u#Y*mfpsca{e`?apEF9$(9# zfZ_n*y1NKFs-Z;ji?v=wIibH3-Sdn|`FU|!%i4?=mcS!p|JO*f*C+PqoRlqU#|a5@ zL(R_mJ~l0CpTU}*KBQ=9gCTCGxu?sL8s+1f>Z7ZrRF#4zUD?XT0S7DeQLY}HZT7z8 zsn(P%Dl+q$ds;Lw`29=9>#;WRZ(`={?P<{N8=nTXZf~TclfaGnk8$n&A`N|l=JdXm zSx`hz((R`Sp)4eoPD)^|bqjh~*$2h&GX|W7&Y9W>De@vP_#ifpAfPkoUfc0^_7+;^ zZCM6pa-~G>3dCza)ZQ@+VWTFVj!A9GRRhv}ou7~69wZdq=n?Rri#Qy<8j+-%uKP3C zw5>#L{`fD0ZjwdbwjA*cmptq4Ht7K0D0x`HQ>rkD_D?!OzyqltNpD zh73mTN2kF?Z|qDT{FN{sOnAx7RER*3%JQD5&Cf^41vQYv>3jOp^E999=weNj!L*UM|JoH z?vsD{Dx8=%?+1Bd2KVM!*k;L&p1gOM@Z-yZg#2`G#T1cn>P1u{vo7Gkeq9X zdyqbZ#N{=1980}=*J&(5d2X~Ht)t+)rFchQP!-s3)z4}~#i*C5psQV7#`aucNSBEA zXE44-`8P#>nM5R=jNqF?cY8>qYYcq4L&kr{8wPJzY8+L|i{c&k1e89Q7$6oXQpW2^ zxsy`ridj()7U0)DGo=Lz8RFE*Qfx2wi{PxieS0Nw2wG;++ZO{X<$zUtb92Ijt|WaR ztyWq|^X1X5@8tgN6x;cgU~OZjBrNzKa_yH5;?ekYv18OaaAT$y1@pINv6iYALII_g zue;cFjW21IK-$>6SCi-TcR15gQ+^!(W7c?}2xxWP5^1De*;xtFwkE%&nk+OFz^;KL zBuQ(3&g@6P>iSz<_Q@ufi>CmkDaJiU8}Y=Ak4;AicM(^Vl>u% z>~4OyX*jVz;LHZ6`ZJ@OqBImB6W}$%(H*LV>H7@DmKMS_2XDf+@Vy(*QqKLd;3}gC zMZ<6&H)qGbrE8Xe#_PuM1TQ7voM$g!@UFrYN|$O+#7Qc=7Bnx?e~OSoGRr<#h5$-# zHGIo@bCvY{ci`K(CWFvI4IgXi3`gO0_lP2;Kn?m@D%|1|10uc$y?g5lIijH`{7=DT zDhm4iq&0(a%_R8I+Tm!4`w(RRDWylk2el zBQ;B8s=K$f(WA*2wnF!)nE3~`3tNy~mvF_{8kx%HEwBdEqhkin!y^_QjWAQ`m@;%o zLgRahJ69iWxGcTZmZhjb)VqM9 zPkNNq8Ygn&j+e0CB&um}b&zt5x^YJwYfgV>&t7`mZD!kFpuEooxy0|JlzaX;U?g%U zIT|L+88@qFW5ce%;Lpl!L4+ zuU^cS!C`TNIAI*3W6w>y1-;Q5YZnrl^*n>-`EQvLSA`ATRk1}9q L?POJPkP!c0$n{c( literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..11ae02670946c05025220b2f1937fdffc3401f24 GIT binary patch literal 2428 zcmZ{lYgAKL7J&Cn2nhrT0pzJ9z$g?M9@2t?O2SJ-c~z@G6$uXk5r$ZiS2YRBL#3-w z?LsO(?u=7hj*lX?f)yl+)F4d3p(9wUgb@&n4)LNj*NJiVW$BOpn3a`vzkBZ9-`P3$ z?34Y8@zIv%&gKAM8M7*KEdUFV0G=scdg_17!Ha3en&?P)!+gljQ-^TGOui~*2LRue z`4VvCs3Q(?i(=xUxDPFae9K_DgZEDuW)>3}_EG82Pw&SRyDYPg3S&k8ceHi6PR$O^ zi?vT|7cSQOUftEd`tiMo*UoiHTxX7l50@^lJ|$C3)DYr#sc~xkk&1BvdM`7 zmTD~8?Ie2~AsxYifw7fNCp{RbKnO&TsluF2N*KsRNMIl_On5Sh0VzTz1L82JX%_~p z!-UhW4Ctl8X)yy+2p;1zYnr2FBkHqmN?*7Rr7lAWR1V>vta87zj)6pk6sn|hYdr*- zcN#23-r9oLn%jM&?=Mx|t=ep?76^1f%|lZYh0b>B*a$ymca2G@?a=n;RIr69x$nh% z%!wep(sxJuDTn+aTU1WFxQ3$R4La-;Y6o|WT%N0LYV=!BPB*ax`n8p45yXRA=R zPD7-9HrO|+6BC6QSMzZm^B`@Xkg+&xMpo(Cw*>i}l3H z{Bz+^Dy!(6p`eTaXz-gKv=)*ek8aw-BhO$Fj$W!Ae#EOJV?!!b3gEYghk>wCls)s~ z+!{T|QQwo0mFWWp%e`#5apSk~4P9&e0MwUitKw|c^Hd)jn^nYAP&qy80Z{mb$%>ss ziyQmFb*(GyV#+38EIRki#LQy{0BulhWr59H#wN_?sO`7b-q`4P9_*->lR|UOOa0j| zKcH>~IY5Q3s;(_~cKE1-zM{mJk~T)x2OOe?`%Z`mFGs8XeM?pvOM5x$n(10m;g-hr z^pz6hCa^Pma7&8U{GzM+XB(=W8oI*q?n~3#9q(m~4{+3zlYOGX%*L&B?Zf~_8Y8Yb z;7IeSLPAe*zlt;-8F;-TKQ}ynZ%XhqHLN+oSA|1L6r;;xSx!Fqr7z7*sW3|<)RNgV z`@ZO!;;6e{P6*1S0?lX{J-ZC5U9+5Nek<_2H9^$yu4d($}oEfV_&Q=q`=X&usN))bt6+(1vL6%k-P4{l)wIBS?mYp~! z^aW8#9Z+h5v2VTFM8zVLY%4q&0PJxG;E|R5PWLthpan+`m(B8$Pa6~d`pTsjXNz!djGMS;6bd?dB)$;YM}-7JGQJ*nV-ZNd4m2G)uNF(x{GIkK12`Z>j~} z|C5uOQr2L@SEb#^SV*(mFKtVO{LfMJ7YYM*(Hp_qeO$0~Bys5pMWSNmQ+|R`S~q{l z!xz9hi)i`KT`Ak(13Y_*oBfB&pil8&X8Pd%uPj+N&XnH{;#`#N*Q{5wU)R+<+EhSJ z2#R|Cc;!0U_xM0i!nmMcI=e(W7CPn@(&PMRn*CaSMjay_?5yL6hJBwzTf+0c?KJ z*|sG;xGteHD^BdZ_fI_qOqYoT%ao zYj?Ny60<%L+1B-Anu!Tu_VxTj=K-p&7UNcMfWq`ktCK(#o-mO5rdw)+&BNOjCaU0E z6?w`JW91we>AJUhL&JkboC18CMN|)SY}waVwfS!%ATM?K%qx)<#hq%2!|)rG0}DYm zV8rraaMO~Gsxfm&oW5W7vxqk?7+mj?3LX2+{0=|TaKg5;jWEIjo2v-6a&%*!78M2}#19)JsMCE<v-fa~J+K2PWc5p4PL-eU=S| z3dMmp^oKX9V~tYJf5p~G#Z0UEGEqeoH$;O$D>&VrO7c@5IN-R~OGy^`z%57idrC5s z8DV=X$)veQ2no!ih{Z}$f-nLeN-~fEDS`(Bau4Mc#|O;swjf}qo8Q%WC>zUsV0DU{ jSlL*BF)1+!X`vL`$0)p8og@4O4<(3+ijO=WF5B}zdYtV& literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..c0bae3e32786f515c11627d80a9f0da1396e1876 GIT binary patch literal 1428 zcmZux4NOy46h8OKqwTA@2b4;ju2e&;u?jMmnQ7>wR*@KOb*RN3sE~+>hE=x#D$+|S zL)~JNZIZ=oQbV@tMzlJk;3}3X1~;9WO-BUR*AbR6g_gOc#X`ILTDG`kd3m|#JLmhp zbMHNQ?}%Yt!8B$j0|3(sSL-$aAQB=FCzEB{zSBF&l3cc?KnF45aa?tNL^2Zd>f*Nn zq-ny7;KZjHBq@5QaP3Obz|<7!^u-s7`{w}A%0gX!QO%>_$LII;ajY(1&BS8g6@9Tm zeZ0nf@Nq}l-ijQ@;2+!e48OZQThn;9<_3zb@_z1sq?1+3%%BQvuh95j zE3gM-ptVz6a6O3g81A=xB+gSomx#^`JHV!pVV($U4PbqV;lhcc|G&jL19)F3HdP|A zMknRM$}J>+pJM9{%W-hUMPgtm^`8uS*SUkL)K=LfRAV8dqk$BIvfo@_#J0e(c>eNnin-|^XlvGt%`+pb;} zrcYKXKL|>fKh_#Rt8br5GA)%UDmXQ7Tk_m-tNyZ>95-!|HKoq)d@?$ht-Uh775vUa z@Bey@FGrcYK6=tsu=y!=tmoORg?F%9x^3jXVK{+|(yAubsxhR=~Fx;IY6^ z0dI4vdS9XQFrSZ;r{oq`^-W0l$p& z`ycL}?=ALKe??_rjGN$4&aQsn@U`x;tWZUQ-zoA;%#VvbyI~~YV?3qZt`8$$LmL>) z76#P9L0-c@P8^znv&)RPCW>JbxX0p_MkEWoHOpOH)Yx}bBLz3b-_ngnWjHmn)^fRe zI}^BKB;*l2QlWmIy?e0d&c>{*0<%_xowDx8%c7BuTvt`#E@eJ#3kWoC==JG+PjK1r z&n+}>lK5IE17#yUF8+iXA6?bZ4zY|2M{hPcl0N1_(OMCYB|hKMvfbQ~xJ!?Xc)5~g z6_IGI_$VhS+E1H%jd1M2OhfZZyP~T?(Voi>$g%tq(*{!b3ja>s^FFLkXR|?J)f%W0 z)VowxGEWT)pNcOnLZQ&Xx)ia1Sb^?>i>`DNZG?^~+WPr$VR?Q!mfvK8v$69CTl}gV zOla4R1s=u)Zu_YlYFMl0nlVkTeoHUoqSO#5SLI5%G^stygh1E?MO;0RjBjsK_ik9r zB}O=r(YO=sDm!&P=@&vAhzcG#Bj?<(Wbp_&Sdy)K;`j`lg~@o-D1WbIkTywp!dkPcnU(B=|ohJ@t$zmhi8WfmmU5|Ye*a=S^4F#aq@Qh#Y+8Hf%GgGWlU zMUGe8AftYW2dYWjkceBWEeuCKuaFaD^{HmdodC?5FLX(kwME{9qa142D2~oCQ2xlX zdesz~?bIj^C*Teh6^HNT#;f_0Ra*ioC!sKd;W?|6DxmQO1I56g1K#L_^zz=(_0DfJ Q82|tP literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..cfdb4ee9c81949eb193938ee97bbd81fbba72927 GIT binary patch literal 1487 zcmah}4NOy46#m}p4-eYHt8BEh{yf|!OPm%{brLnaK^+hyWsB1pWFRgw6A>cY?2yqb zQ(&1+6Juu70A^;m84%qB9Y`NkGdH*RGbdGuec&8MqLgT3!T#*FbBS4&EHC%md%knd zchCFo&HXGdH%k{cISzm>C)@fKz|bWFPEDsZn~$!ile&0WmKEX1*L1ddFGXU@vsYID zn#9OpaNv-Uf-xWDEMFRPTdUV3#JuOXPXMYlIo8)#*l*q+cr$lIoFra3xi46j79duB zygs{a(c?qU@4VYOa_^UeH)=BrN?K=yhdt{TG@pJno{S*@_2d-}tiT<1hA&+l7Of)R z%aj{gLCwmkR#B^z)^fzi5f{(QO%IzYi)S`YH7IJ!?Wy*P0P_nXM6}hC7 zoo?=!PTWay88|f5CAiQ&cLYWH)Q2eXtK2>0iv2 zf{D;aO`GbpgB94QX~$z5tWhH+$)Q-nMU9&sNN0E^YVygG6wHX2F)oTjDJ4bR>I6zh zRw_3#bO*3Snh3iyh7yRg*eEfWHdTbrqqyKnXX;O=1hj*a9(^Oa86xh_v;j#?W|N^{ zr{B`PWD*nBT`)B`Gkq_&Y!air3r9v9Q(!)`y;zQI>g9TV8g0}ee;Xa7rO6%+EJFO5 zBN-pZv+~IL{5VhgsKt=K(Dk@%%Le_gk30pt3f}J*eWCkv%tIR#dBuhsGx;vZ!B2T4 z^QaTp8Px0ieCM8z?#DCw?C|oIb#q#Wqxth{?#0SH&)6`sUo(=H_XEw_)mF)(0W0Sg z&a!R08tgCFbnf>=DOq4yGJoII;MPj4Fy@O@is;dxJoHZG!-jaN;kpI`me72 zZ4&AT$Klr3^B#DdN^!>tl{<3TH#2RugD(^*n_8TZHCCtI@7taB3L-34-y> z{!Al1wwCZ~HpJ8nCX~VP4QCAax1|7-X6$tb(>1 z Date: Fri, 18 Aug 2023 11:59:04 +0200 Subject: [PATCH 2/8] GH-272 Adds translations --- .../ri/resources/MessageBundle_de.properties | 43 +++++++++++++++++++ .../ri/resources/MessageBundle_fr.properties | 43 +++++++++++++++++++ 2 files changed, 86 insertions(+) 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 5959be0ff..9f7ddf10c 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 @@ -170,6 +170,16 @@ viewer.menu.window.9.mnemonic=9 viewer.menu.help.label=Hilfe viewer.menu.help.mnemonic=H viewer.menu.help.about.label=\u00DCber den ICEpdf Viewer... +## 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=\ @@ -478,6 +488,39 @@ viewer.utilityPane.signatures.cert.dialog.info.sha1.value={0} viewer.utilityPane.signatures.verify.initializingMessage.label=Pr\u00FCfe {0} von {1} Signaturen viewer.utilityPane.signatures.verify.completeMessage.label=Pr\u00FCfprozess abgeschlossen viewer.utilityPane.signatures.verify.validating.label=Signatur pr\u00FCfen... +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 validation dialog. viewer.annotation.signature.validation.dialog.title=Zusammenfassung der Signaturg\u00FCltigkeit viewer.annotation.signature.validation.dialog.close.button.label=Schliessen 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 024636356..7b1516fae 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 @@ -170,6 +170,16 @@ viewer.menu.window.9.mnemonic=9 viewer.menu.help.label=Aide viewer.menu.help.mnemonic=H 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... ## General error dialog viewer.dialog.error.exception.title=ICEpdf - Exception viewer.dialog.error.exception.msg=\ @@ -469,6 +479,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 validation dialog. viewer.annotation.signature.validation.dialog.title=R\u00E9sum\u00E9 de la validation de la signature viewer.annotation.signature.validation.dialog.close.button.label=Fermer From 1a7631736550d989311d4fe7e36625ddd15f1664 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guillaume=20T=C3=A2che?= Date: Wed, 6 Sep 2023 14:32:51 +0200 Subject: [PATCH 3/8] GH-272 Uses getImportExportHandler to allow override --- .../annotations/summary/mainpanel/SummaryController.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) 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 index 28ce85590..ecf556460 100644 --- 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 @@ -57,7 +57,6 @@ public class SummaryController implements MutableDocument { private final Map annotationToColorPanel; private final GroupManager groupManager; - private final ImportExportHandler ieHandler; private final MouseListener mouseListener; private final ComponentListener componentListener; @@ -90,7 +89,6 @@ public SummaryController(final Frame frame, final Controller controller, final A this.yCoordinates = new HashMap<>(); this.dragManager = createDragAndLinkManager(); this.groupManager = createGroupManager(); - this.ieHandler = IMPORT_EXPORT_HANDLER; this.mouseListener = new SummaryMouseListener(); this.componentListener = new SummaryComponentListener(); this.propertyListener = new PropertiesListener(); @@ -169,7 +167,7 @@ public boolean hasLoaded() { public void exportFormat(final OutputStream output) { final ResourceBundle messageBundle = controller.getMessageBundle(); try { - ieHandler.exportFormat(annotationNamedColorPanels, output); + 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") @@ -195,7 +193,7 @@ public void exportFormat(final OutputStream output) { public Map> canImport(final InputStream inputStream, final boolean partial) { try { final Map> compToCell = - ieHandler.validateImport(annotationNamedColorPanels, inputStream, partial); + getImportExportHandler().validateImport(annotationNamedColorPanels, inputStream, partial); return compToCell != null && (partial || compToCell.keySet().containsAll( new HashSet<>(annotationToBox.values()))) ? compToCell : null; } catch (final Exception e) { @@ -236,7 +234,7 @@ public boolean importFormat(final Map Date: Thu, 19 Oct 2023 17:11:35 +0200 Subject: [PATCH 4/8] GH-272 Makes AnnotationSummaryBox font size not modify underlying annotation font size, retrieve annotation font size on popup init --- .../ri/common/views/annotations/PopupAnnotationComponent.java | 4 +++- .../views/annotations/summary/AnnotationSummaryBox.java | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) 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 1f06046fe..5285e0a27 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 @@ -368,7 +368,7 @@ private void buildGUI() { String contents = annotation.getParent() != null ? annotation.getParent().getContents() : ""; textArea = new JTextArea(contents != null ? contents : ""); - textArea.setFont(new JLabel().getFont()); + textArea.setFont(new JLabel().getFont().deriveFont(annotation.getTextAreaFontsize())); textArea.setWrapStyleWord(true); textArea.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createLineBorder(PopupAnnotation.BORDER_COLOR), BorderFactory.createEmptyBorder(2, 2, 2, 2))); @@ -381,11 +381,13 @@ private void buildGUI() { // creation date creationLabel = new JLabel(); + creationLabel.setFont(new JLabel().getFont().deriveFont(annotation.getHeaderLabelsFontSize())); refreshCreationLabel(); // title, user name. String title = selectedMarkupAnnotation != null ? selectedMarkupAnnotation.getFormattedTitleText() : ""; titleLabel = new JLabel(title); + titleLabel.setFont(new JLabel().getFont().deriveFont(annotation.getHeaderLabelsFontSize())); // main layout panel GridBagLayout layout = new GridBagLayout(); diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/AnnotationSummaryBox.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/AnnotationSummaryBox.java index b134c5d62..e09bb0411 100644 --- a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/AnnotationSummaryBox.java +++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/AnnotationSummaryBox.java @@ -345,7 +345,9 @@ public String getDebuggable() { @Override public void setFontSize(final int size) { - super.setFontSize(size); + titleLabel.setFont(titleLabel.getFont().deriveFont(size)); + creationLabel.setFont(titleLabel.getFont().deriveFont(size)); + textArea.setFont(titleLabel.getFont().deriveFont(size)); } @Override From f34e7408da093f08e57580df2c87b2d7a9a82f38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guillaume=20T=C3=A2che?= Date: Fri, 20 Oct 2023 10:16:44 +0200 Subject: [PATCH 5/8] GH-272 Fixes AnnotationSummaryBox size update --- .../views/annotations/summary/AnnotationSummaryBox.java | 2 +- .../annotations/summary/AnnotationSummaryComponent.java | 2 +- .../views/annotations/summary/AnnotationSummaryGroup.java | 2 +- .../summary/colorpanel/ColorPanelController.java | 3 +-- .../annotations/summary/mainpanel/SummaryController.java | 8 ++++---- 5 files changed, 8 insertions(+), 9 deletions(-) diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/AnnotationSummaryBox.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/AnnotationSummaryBox.java index e09bb0411..8c92c1317 100644 --- a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/AnnotationSummaryBox.java +++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/AnnotationSummaryBox.java @@ -344,7 +344,7 @@ public String getDebuggable() { } @Override - public void setFontSize(final int size) { + public void setFontSize(final float size) { titleLabel.setFont(titleLabel.getFont().deriveFont(size)); creationLabel.setFont(titleLabel.getFont().deriveFont(size)); textArea.setFont(titleLabel.getFont().deriveFont(size)); 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 index 902ff23a0..14830423e 100644 --- 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 @@ -123,7 +123,7 @@ public interface AnnotationSummaryComponent { * * @param size The new font size */ - void setFontSize(int size); + void setFontSize(float size); /** * Sets the font family 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 index 5d163b314..c13928d3e 100644 --- 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 @@ -261,7 +261,7 @@ public String getDebuggable() { } @Override - public void setFontSize(final int size) { + public void setFontSize(final float size) { components.forEach(c -> c.setFontSize(size)); } 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 index 3396a91ba..bb33a91b7 100644 --- 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 @@ -21,13 +21,12 @@ private class PropertyListener implements PropertyChangeListener { @Override public void propertyChange(final PropertyChangeEvent evt) { final Object newValue = evt.getNewValue(); - final Object oldValue = evt.getOldValue(); 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((int) newValue); + ((AnnotationSummaryComponent) component).setFontSize((float) newValue); } } } 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 index ecf556460..61007b4e5 100644 --- 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 @@ -288,15 +288,15 @@ void addFontListeners() { 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, (int) tmp.getValue()); + 0, (float) value); } } - propertiesManager.setInt(ViewerPropertiesManager.PROPERTY_ANNOTATION_SUMMARY_FONT_SIZE, - (int) tmp.getValue()); - setFontSize((int) tmp.getValue()); + propertiesManager.setInt(ViewerPropertiesManager.PROPERTY_ANNOTATION_SUMMARY_FONT_SIZE, value); + setFontSize(value); } }); summaryPanel.getFontNameBox().addItemListener(itemEvent -> { From 59066f6d63c761f1f612436e41a1f6cf9b42ee67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guillaume=20T=C3=A2che?= Date: Mon, 29 Jan 2024 17:02:09 +0100 Subject: [PATCH 6/8] GH-272 Removes usages of FileSeekableInputStream --- .../mainpanel/AnnotationSummaryPanel.java | 39 +++++++++------- .../summary/mainpanel/SummaryController.java | 45 ++++++++++++------- 2 files changed, 51 insertions(+), 33 deletions(-) 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 index 36ffd6f91..811917f71 100644 --- 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 @@ -15,7 +15,6 @@ */ package org.icepdf.ri.common.views.annotations.summary.mainpanel; -import com.twelvemonkeys.io.FileSeekableStream; import org.icepdf.core.util.Defs; import org.icepdf.ri.common.utility.annotation.properties.FreeTextAnnotationPanel; import org.icepdf.ri.common.utility.annotation.properties.ValueLabelItem; @@ -33,18 +32,25 @@ 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 { @@ -358,14 +364,8 @@ private void importPerformed(final ActionEvent actionEvent) { final String filename = chooser.getFile(); final String dir = chooser.getDirectory(); if (filename != null && dir != null) { - ViewerPropertiesManager.getInstance().set(ViewerPropertiesManager.PROPERTY_ANNOTATION_SUMMARY_IMPORT_FILE, - dir + filename); - try { - final File file = new File(dir + filename); - controller.importFormat(new FileSeekableStream(file), true); - } catch (final FileNotFoundException ignored) { - - } + final String path = dir + filename; + importFile(path); } } else { final JFileChooser chooser = new JFileChooser(); @@ -384,17 +384,24 @@ private void importPerformed(final ActionEvent actionEvent) { final int ret = chooser.showSaveDialog(controller.getFrame()); if (ret == JFileChooser.APPROVE_OPTION) { final File file = chooser.getSelectedFile(); - ViewerPropertiesManager.getInstance().set(ViewerPropertiesManager.PROPERTY_ANNOTATION_SUMMARY_IMPORT_FILE, - file.getAbsolutePath()); - try { - controller.importFormat(new FileSeekableStream(file), true); - } catch (final FileNotFoundException ignored) { - - } + 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()); } 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 index 61007b4e5..befa53409 100644 --- 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 @@ -1,7 +1,5 @@ package org.icepdf.ri.common.views.annotations.summary.mainpanel; -import com.twelvemonkeys.io.FileSeekableStream; -import com.twelvemonkeys.io.SeekableInputStream; import org.icepdf.core.pobjects.Document; import org.icepdf.core.pobjects.Reference; import org.icepdf.core.pobjects.annotations.Annotation; @@ -23,10 +21,23 @@ import javax.swing.*; import java.awt.*; -import java.awt.event.*; +import java.awt.event.ComponentAdapter; +import java.awt.event.ComponentEvent; +import java.awt.event.ComponentListener; +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.*; +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.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Paths; import java.text.MessageFormat; import java.util.List; import java.util.*; @@ -207,7 +218,7 @@ public Map> canImport(final I * @param inputStream The input stream * @return true if the import was unsuccessful and the user wants to delete the file */ - public boolean importFormat(final SeekableInputStream inputStream, final boolean partial) { + public boolean importFormat(final InputStream inputStream, final boolean partial) { try { final Map> compToCell = canImport(inputStream, partial); inputStream.reset(); @@ -255,12 +266,6 @@ public boolean importFormat(final Map Date: Thu, 13 Jun 2024 12:25:07 +0200 Subject: [PATCH 7/8] GH-272 Fixes some exceptions (especially when no color labels are defined), fixes moveComponent to avoid StackOverflow, minor cleanup --- .../org/icepdf/ri/common/SwingController.java | 2 +- .../markup/FindMarkupAnnotationTask.java | 8 +- .../markup/MarkupAnnotationPanel.java | 10 +- .../summary/AnnotationSummaryBox.java | 22 +- .../summary/AnnotationSummaryFrame.java | 2 +- .../summary/colorpanel/ColorLabelPanel.java | 28 +- .../colorpanel/DraggableAnnotationPanel.java | 30 +- .../colorpanel/DraggablePanelController.java | 98 +++---- .../mainpanel/AnnotationSummaryPanel.java | 24 +- .../summary/mainpanel/DragAndLinkManager.java | 22 +- .../summary/mainpanel/LinkLabel.java | 105 +++++++ .../mainpanel/NoOpImportExportHandler.java | 2 +- .../summary/mainpanel/SummaryController.java | 269 ++++++------------ .../ri/common/widgets/DragDropColorList.java | 6 +- 14 files changed, 331 insertions(+), 297 deletions(-) create mode 100644 viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/mainpanel/LinkLabel.java 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 84787e48d..bac3be57e 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 @@ -5064,7 +5064,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) { 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/annotations/summary/AnnotationSummaryBox.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/AnnotationSummaryBox.java index 8c92c1317..c38d4a941 100644 --- a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/AnnotationSummaryBox.java +++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/summary/AnnotationSummaryBox.java @@ -22,7 +22,11 @@ import org.icepdf.core.pobjects.annotations.PopupAnnotation; import org.icepdf.core.pobjects.annotations.TextMarkupAnnotation; import org.icepdf.core.util.SystemProperties; -import org.icepdf.ri.common.views.*; +import org.icepdf.ri.common.views.AbstractPageViewComponent; +import org.icepdf.ri.common.views.AnnotationComponent; +import org.icepdf.ri.common.views.Controller; +import org.icepdf.ri.common.views.DocumentViewController; +import org.icepdf.ri.common.views.PageViewComponentImpl; import org.icepdf.ri.common.views.annotations.AbstractAnnotationComponent; import org.icepdf.ri.common.views.annotations.MarkupAnnotationComponent; import org.icepdf.ri.common.views.annotations.PopupAnnotationComponent; @@ -41,7 +45,12 @@ import java.awt.event.FocusListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.UUID; import java.util.logging.Level; @@ -192,11 +201,14 @@ public void actionPerformed(final ActionEvent e) { } } + @Override public Color getColor() { - if (getAnnotationComponent() != null && getAnnotationComponent().getAnnotation() != null) { - return getAnnotationComponent().getAnnotation().getColor(); - } else { + if (getAnnotationComponent() == null || + getAnnotationComponent().getAnnotation() == null || + getAnnotationComponent().getAnnotation().getColor() == null) { return popupBackgroundColor; + } else { + return getAnnotationComponent().getAnnotation().getColor(); } } 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 1a94e92e1..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 @@ -46,7 +46,7 @@ public void windowClosing(final WindowEvent e) { } public void saveChanges() { - if (annotationSummaryPanel.getController().hasChanged()) { + 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()}); 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 index cdb23abc5..2f7b1f813 100644 --- 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 @@ -31,6 +31,8 @@ import java.util.UUID; import java.util.function.Predicate; +import static java.util.Objects.requireNonNull; + /** * */ @@ -43,14 +45,12 @@ public class ColorLabelPanel extends JPanel { public ColorLabelPanel(final Frame frame, final DragDropColorList.ColorLabel colorLabel, final SummaryController summaryController) { this.controller = createColorPanelController(); - this.colorLabel = colorLabel; - this.summaryController = summaryController; + this.colorLabel = requireNonNull(colorLabel); + this.summaryController = requireNonNull(summaryController); // setup the gui setLayout(new BorderLayout()); - if (colorLabel != null) { - add(new JLabel("

" + colorLabel.getLabel() + "

", JLabel.CENTER), BorderLayout.NORTH); - } + add(new JLabel("

" + colorLabel.getLabel() + "

", JLabel.CENTER), BorderLayout.NORTH); draggableAnnotationPanel = createDraggableAnnotationPanel(frame); draggableAnnotationPanel.addMouseWheelListener(e -> draggableAnnotationPanel.getParent() .dispatchEvent(SwingUtilities.convertMouseEvent(draggableAnnotationPanel, e, draggableAnnotationPanel.getParent()))); @@ -86,7 +86,9 @@ public void compact(final UUID uuid) { public AnnotationSummaryBox addAnnotation(final MarkupAnnotation markupAnnotation, final int y) { final PopupAnnotation popupAnnotation = markupAnnotation.getPopupAnnotation(); - if (popupAnnotation != null) { + if (popupAnnotation == null) { + return null; + } else { final List pageComponents = summaryController.getController().getDocumentViewController().getDocumentViewModel().getPageComponents(); final int pageIndex = markupAnnotation.getPageIndex(); @@ -96,17 +98,17 @@ public AnnotationSummaryBox addAnnotation(final MarkupAnnotation markupAnnotatio popupAnnotationComponent.setVisible(true); popupAnnotationComponent.removeMouseListeners(); final AnnotationSummaryGroup parent = summaryController.getGroupManager().getParentOf(markupAnnotation); - if (parent != null) { - parent.addComponent(popupAnnotationComponent); - } else { + 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; - } else return null; + } } protected AnnotationSummaryBox createSummaryBox(final PopupAnnotation popupAnnotation, @@ -139,10 +141,10 @@ public void addComponent(final AnnotationSummaryComponent component) { } public AnnotationSummaryBox addAnnotation(final MarkupAnnotation markupAnnotation) { - if (!markupAnnotation.isInReplyTo()) { - return addAnnotation(markupAnnotation, -1); - } else { + if (markupAnnotation.isInReplyTo()) { return null; + } else { + return addAnnotation(markupAnnotation, -1); } } 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 index 0289df113..bcd26ebf9 100644 --- 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 @@ -24,8 +24,12 @@ 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.*; +import java.util.UUID; import java.util.function.Predicate; import java.util.logging.Logger; @@ -61,7 +65,7 @@ public Component add(final AnnotationSummaryComponent c, final int pos, final bo public boolean contains(final MarkupAnnotation annotation) { return Arrays.stream(getComponents()) - .filter(c -> c instanceof AnnotationSummaryComponent) + .filter(AnnotationSummaryComponent.class::isInstance) .flatMap(c -> ((AnnotationSummaryComponent) c).getAnnotations().stream()) .anyMatch(a -> a.getPObjectReference().equals(annotation.getPObjectReference())); } @@ -72,10 +76,10 @@ public Component add(final Component c, final int pos) { panelController.setNeverDragged(false); } if (panelController.isNeverDragged()) { - if (pos != -1) { - super.add(c, pos); - } else { + if (pos == -1) { super.add(c); + } else { + super.add(c, pos); } } else { if (pos != -1) { @@ -100,7 +104,7 @@ public Component add(final Component c, final int pos) { public int getFirstPosForComponents(final Collection components) { final List sorted = getSortedList(components, true); - return sorted != null ? getPositionFor(sorted.get(0)) : -1; + return sorted == null ? -1 : getPositionFor(sorted.get(0)); } public int getPositionFor(final Component c) { @@ -211,24 +215,22 @@ public void checkForOverlap(final UUID uuid) { final Component[] comps = getComponents(); Arrays.sort(comps, new ComponentBoundsCompare()); if (comps.length > 1) { - checkForOverlap(uuid, comps[0], Arrays.copyOfRange(comps, 1, comps.length)); + checkForOverlap(uuid, comps, 1); } } - private static void checkForOverlap(final UUID uuid, final Component refComp, final Component[] components) { - if (components.length > 0) { - final Component curComp = components[0]; + 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); } - if (components.length > 1) { - checkForOverlap(uuid, curComp, Arrays.copyOfRange(components, 1, components.length)); - } + checkForOverlap(uuid, components, index + 1); } - } public void compact(final UUID uuid) { 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 index 13bf6e04b..cbb4b3c88 100644 --- 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 @@ -16,8 +16,14 @@ 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.*; +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; @@ -362,54 +368,46 @@ public void mouseMoved(final MouseEvent e) { } } - public void moveComponent(final Component dragComponent, final boolean snap) { - if (dragComponent != null) { - final int padding = 10; + private void moveComponent(final Component draggedComponent, final boolean snap) { + if (draggedComponent != null) { panel.sortComponents(); - final Component[] comps = panel.getComponents(); - final int draggedIndex = findComponentAt(comps, dragComponent); - final 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().contains(dragBounds) && comp instanceof AnnotationSummaryGroup) { - summaryController.getGroupManager().moveIntoGroup((AnnotationSummaryComponent) dragComponent, - ((AnnotationSummaryGroup) comp).getColor(), comp.getName()); - return; - } else 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, snap); - } else { - comp.setLocation(dragBounds.x, dragComponent.getY() + dragComponent.getHeight() + padding); - moveComponent(comp, snap); - } - } - } + innerMove(draggedComponent, snap); + } + } - if (draggedIndex == 0) { - // make sure the component y > padding - if (dragComponent.getY() < padding) { - dragComponent.setLocation(dragComponent.getX(), padding); - moveComponent(dragComponent, 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); } - if (draggedIndex >= 1) { - comp = comps[draggedIndex - 1]; - final int offset = dragComponent.getY() - (comp.getY() + comp.getHeight()); - if (offset < padding) { - dragComponent.setLocation(dragComponent.getX(), dragComponent.getY() + (padding - offset)); - moveComponent(dragComponent, snap); + } + + 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(); - ((AnnotationSummaryComponent) dragComponent).fireComponentMoved(snap, true, UUID.randomUUID()); } + + panel.revalidate(); + moved.forEach(c -> c.fireComponentMoved(snap, true, UUID.randomUUID())); } private int findComponentAt(final Component[] comps, final Component dragComponent) { @@ -441,10 +439,12 @@ private AnnotationSummaryComponent getDeepestComponentAt(final Component c, fina final Component child = c.getComponentAt(p); if (child instanceof AnnotationSummaryComponent) { final Point newP = SwingUtilities.convertPoint(c, p, child); - if (child != c) { + if (child == c) { + return (AnnotationSummaryComponent) child; + } else { final AnnotationSummaryComponent deepest = getDeepestComponentAt(child, newP); return deepest == null ? (AnnotationSummaryComponent) child : deepest; - } else return (AnnotationSummaryComponent) child; + } } else if (c instanceof AnnotationSummaryComponent) { return (AnnotationSummaryComponent) c; } else return null; @@ -463,17 +463,19 @@ private List getAllComponentsAt(final Component c, f final Component child = c.getComponentAt(p); if (child instanceof AnnotationSummaryComponent) { //Sometimes StackOverflow for some reason - if (!list.contains(child)) { + 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; + } } else return list; } public AnnotationSummaryComponent findComponentFor(final Predicate filter) { final List components = Arrays.stream(panel.getComponents()) - .map(c -> (AnnotationSummaryComponent) c).collect(Collectors.toList()); + .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; 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 index 811917f71..84340872e 100644 --- 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 @@ -70,7 +70,7 @@ public class AnnotationSummaryPanel extends JPanel { private static final int DEFAULT_FONT_SIZE_INDEX = 5; - private static final List fonts = Arrays.stream(GraphicsEnvironment.getLocalGraphicsEnvironment() + 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()); @@ -88,8 +88,10 @@ public AnnotationSummaryPanel(final Frame frame, final Controller controller) { // add key listeners for ctr, 0, -, = : reset, decrease and increase font size. addFontSizeBindings(); addMouseWheelListener(e -> { - fontSizeBox.setSelectedIndex(Math.max(0, Math.min(fontSizeBox.getSelectedIndex() - e.getWheelRotation(), - fontSizeBox.getItemCount() - 1))); + if (e.isControlDown()) { + fontSizeBox.setSelectedIndex(Math.max(0, Math.min(fontSizeBox.getSelectedIndex() - e.getWheelRotation(), + fontSizeBox.getItemCount() - 1))); + } }); } @@ -166,7 +168,7 @@ protected void buildStatusToolBarPanel() { idx = Math.min(sizeValues.size() - 1, -(idx + 1)); } fontSizeBox = new JComboBox<>(sizes); - fontNameBox = new JComboBox<>(fonts.toArray(new ValueLabelItem[0])); + 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())); @@ -214,6 +216,8 @@ public void actionPerformed(final ActionEvent actionEvent) { 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); @@ -233,7 +237,7 @@ public void refreshPanelLayout() { add(statusToolbarPanel, BorderLayout.SOUTH); final ArrayList colorLabels = DragDropColorList.retrieveColorLabels(); - final int numberOfPanels = colorLabels != null && !colorLabels.isEmpty() ? colorLabels.size() : 1; + 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); @@ -297,7 +301,7 @@ void addAbsolute(final JPanel layout, final Component component, final int x, fi * @param component The component * @param l The label to add */ - public void addLabel(final AnnotationSummaryComponent component, final SummaryController.LinkLabel l) { + 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); @@ -312,7 +316,7 @@ public void addLabel(final AnnotationSummaryComponent component, final SummaryCo * @param c The component * @param l The LinkLabel */ - public void updateLabel(final Component c, final SummaryController.LinkLabel l) { + 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); @@ -324,11 +328,11 @@ public void updateLabel(final Component c, final SummaryController.LinkLabel l) * * @param l The LinkLabel to remove */ - public void removeLabel(final SummaryController.LinkLabel l) { + public void removeLabel(final LinkLabel l) { annotationsPanel.remove(l); } - private int computeX(final Component c, final SummaryController.LinkLabel 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(); @@ -337,7 +341,7 @@ private int computeX(final Component c, final SummaryController.LinkLabel l) { (full ? image.getIconWidth() / 4 : image.getIconWidth() * 2 / 3); } - private int computeY(final Component c, final SummaryController.LinkLabel l) { + 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(); 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 index d2e225825..d8bbae65e 100644 --- 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 @@ -439,16 +439,18 @@ public void componentMoved(final AnnotationSummaryComponent c, final boolean sna colorToComps.put(c.getColor(), colorComponents); if (snap) { final ColorLabelPanel panel = controller.getColorPanelFor(c); - 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 (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; + } } } } 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 index 326f6857d..c9ab343bd 100644 --- 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 @@ -34,6 +34,6 @@ public void importFormat(final Map annotationNamedColorPanels; + protected List annotationNamedColorPanels; private int fontSize; private final Map annotationToBox; @@ -70,8 +67,6 @@ public class SummaryController implements MutableDocument { private final GroupManager groupManager; private final MouseListener mouseListener; - private final ComponentListener componentListener; - private final PropertyChangeListener propertyListener; private final Map directLinks; private final Map leftLinks; @@ -101,12 +96,10 @@ public SummaryController(final Frame frame, final Controller controller, final A this.dragManager = createDragAndLinkManager(); this.groupManager = createGroupManager(); this.mouseListener = new SummaryMouseListener(); - this.componentListener = new SummaryComponentListener(); - this.propertyListener = new PropertiesListener(); this.fontSize = new JLabel().getFont().getSize(); LinkLabel.rescaleAll(192 / fontSize); - summaryPanel.addComponentListener(componentListener); - controller.getDocumentViewController().getPropertyChangeSupport().addPropertyChangeListener(propertyListener); + summaryPanel.addComponentListener(new SummaryComponentListener()); + controller.getDocumentViewController().getPropertyChangeSupport().addPropertyChangeListener(new PropertiesListener()); } protected DragAndLinkManager createDragAndLinkManager() { @@ -118,7 +111,11 @@ protected GroupManager createGroupManager() { } protected ImportExportHandler getImportExportHandler() { - return IMPORT_EXPORT_HANDLER; + return DEFAULT_IMPORT_EXPORT_HANDLER; + } + + public boolean canSave(){ + return getImportExportHandler() != DEFAULT_IMPORT_EXPORT_HANDLER; } public Frame getFrame() { @@ -419,7 +416,7 @@ void refreshCoordinates() { xCoordinates.clear(); yCoordinates.clear(); if (summaryPanel.getAnnotationsPanel() != null) { - Arrays.stream(summaryPanel.getAnnotationsPanel().getComponents()).filter(c -> c instanceof ColorLabelPanel).forEach(clp -> { + 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 -> { @@ -451,8 +448,8 @@ public void refreshDocumentInstance() { if (controller.getDocument() != null) { // get the named colour and build out the draggable panels. final Document document = controller.getDocument(); - final ArrayList colorLabels = DragDropColorList.retrieveColorLabels(); - final int numberOfPanels = colorLabels != null ? colorLabels.size() : 1; + final List colorLabels = DragDropColorList.retrieveColorLabels(); + final int numberOfPanels = colorLabels.size(); if (annotationNamedColorPanels != null) annotationNamedColorPanels.clear(); annotationNamedColorPanels = new ArrayList<>(numberOfPanels); annotationToColorPanel.clear(); @@ -460,7 +457,28 @@ public void refreshDocumentInstance() { groupManager.refreshGroups(); - if (colorLabels != null && !colorLabels.isEmpty()) { + 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 = new DragDropColorList.ColorLabel(Color.WHITE, "default"); + 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); @@ -482,25 +500,6 @@ public void refreshDocumentInstance() { } groupManager.addGroupsToPanel(annotationColumnPanel); } - // check to make sure a label has - } else { - // other wise just one big panel with all the named colors. - final ColorLabelPanel annotationColumnPanel = createColorLabelPanel(frame, null); - annotationNamedColorPanels.add(annotationColumnPanel); - for (int i = 0, max = document.getNumberOfPages(); i < max; i++) { - final List annotations = document.getPageTree().getPage(i).getAnnotations(); - if (annotations != null) { - for (final Annotation annotation : annotations) { - if (annotation instanceof MarkupAnnotation) { - annotationToColorPanel.put(annotation.getPObjectReference(), annotationColumnPanel); - final AnnotationSummaryBox box = annotationColumnPanel.addAnnotation((MarkupAnnotation) annotation); - if (box != null) { - annotationToBox.put(annotation.getPObjectReference(), box); - } - } - } - } - } } } refreshPanelLayout(); @@ -521,49 +520,57 @@ private void tryImportSummaryFile() { } public OutputStream getDefaultSummaryOutputStream() { - final File file = getDefaultSummaryFile(); - if (file != null) { + final String file = getDefaultSummaryFile(); + if (file == null) { + return null; + } else { try { - return new FileOutputStream(file); - } catch (final FileNotFoundException e) { + return Files.newOutputStream(Paths.get(file)); + } catch (final IOException e) { + LOG.log(Level.SEVERE, "Error getting output to " + file, e); return null; } - } else { - return null; } } public InputStream getDefaultSummaryInputStream() { - final File file = getDefaultSummaryFile(); - if (file != null) { - try { - return new BufferedInputStream(Files.newInputStream(Paths.get(file.getAbsolutePath()))); - } catch (final FileNotFoundException e) { - return null; - } catch (final IOException e) { - LOG.log(Level.SEVERE, "Error getting " + file, e); + 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; } - } else { - return null; } } public void deleteSummaryFile() { - final File file = getDefaultSummaryFile(); - if (file != null && file.exists()) { - file.delete(); + 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 File getDefaultSummaryFile() { - if (controller.getDocument() != null) { + 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 new File(folder + '/' + filename + '.' + IMPORT_EXPORT_HANDLER.getFileExtension()); - } else { - return null; + return folder + File.separator + filename + '.' + DEFAULT_IMPORT_EXPORT_HANDLER.getFileExtension(); } } @@ -578,18 +585,20 @@ ColorLabelPanel getColorPanelFor(final MarkupAnnotation annot, final boolean cac if (cached && annotationToColorPanel.containsKey(annot.getPObjectReference())) { return annotationToColorPanel.get(annot.getPObjectReference()); } else { - final ArrayList colorLabels = DragDropColorList.retrieveColorLabels(); - if (colorLabels != null && !colorLabels.isEmpty() && annotationNamedColorPanels != null) { + final List colorLabels = DragDropColorList.retrieveColorLabels(); + if (colorLabels.isEmpty() || annotationNamedColorPanels == null) { + if (annotationNamedColorPanels != null && !annotationNamedColorPanels.isEmpty()) { + return annotationNamedColorPanels.get(0); + } else { + return null; + } + } else { for (final ColorLabelPanel annotationColumnPanel : annotationNamedColorPanels) { if (annotationColumnPanel.getColorLabel().getColor().equals(annot.getColor())) { return annotationColumnPanel; } } return null; - } else if (annotationNamedColorPanels != null && !annotationNamedColorPanels.isEmpty()) { - return annotationNamedColorPanels.get(0); - } else { - return null; } } } @@ -642,13 +651,13 @@ public Color getRightColor(final DraggableAnnotationPanel panel) { private int idxOf(final DraggableAnnotationPanel panel) { final List components = Arrays.asList(summaryPanel.getAnnotationsPanel().getComponents()); - return components.stream().filter(c -> c instanceof ColorLabelPanel).map(c -> ((ColorLabelPanel) c) + 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(c -> - c instanceof ColorLabelPanel).collect(Collectors.toList()); + 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; } @@ -672,7 +681,7 @@ public Controller getController() { return controller; } - public ArrayList getAnnotationNamedColorPanels() { + public List getAnnotationNamedColorPanels() { return annotationNamedColorPanels; } @@ -798,10 +807,10 @@ private void addLabels(final AnnotationSummaryComponent key, final AnnotationSum } 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))); + 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()))); } /** @@ -874,10 +883,14 @@ public void mouseMoved(final MouseEvent e) { 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) { + if (comp1 == null) { + removeLabel(); + } else { final Color color2 = getColorForIdx(xIdx / 2); final AnnotationSummaryComponent comp2 = getComponentForY(color2, y); - if (comp2 != null) { + if (comp2 == null) { + removeLabel(); + } else { this.c1 = comp1; this.c2 = comp2; if (!dragManager.areLinked(c1, color2) && !dragManager.areLinked(c2, color1)) { @@ -895,11 +908,7 @@ public void mouseMoved(final MouseEvent e) { } summaryPanel.refreshAnnotationPanel(); } - } else { - removeLabel(); } - } else { - removeLabel(); } } else { @@ -984,106 +993,6 @@ public void mouseExited(final MouseEvent e) { } } - /** - * Class representing a label with a Link icon - */ - static 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)); - } - } - - private class PropertiesListener implements PropertyChangeListener { protected MarkupAnnotation lastSelectedMarkupAnnotation; 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; } From 789677f415b4d8fbad560b1fa04cd4680856ac5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guillaume=20T=C3=A2che?= Date: Thu, 13 Jun 2024 14:02:02 +0200 Subject: [PATCH 8/8] Fixes some menu problems when there are no labeled colors --- .../summary/AnnotationSummaryGroup.java | 18 +++- .../summary/mainpanel/GroupManager.java | 18 ++-- .../summary/mainpanel/SummaryController.java | 47 ++++++----- .../summary/menu/MenuFactoryHelper.java | 84 ++++++++++++------- 4 files changed, 103 insertions(+), 64 deletions(-) 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 index c13928d3e..51f8f9c03 100644 --- 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 @@ -10,11 +10,19 @@ 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.*; +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 */ @@ -32,8 +40,8 @@ public class AnnotationSummaryGroup extends MoveableComponentsPanel implements A 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 = name; - this.summaryController = summaryController; + this.name = requireNonNull(name); + this.summaryController = requireNonNull(summaryController); this.id = UUID.randomUUID(); setRequestFocusEnabled(true); setFocusable(true); @@ -43,7 +51,9 @@ public AnnotationSummaryGroup(final Collection compo } public Color getColor() { - if (!components.isEmpty()) { + if (summaryController.isSingleDefaultColor()) { + color = summaryController.getAnnotationNamedColorPanels().get(0).getColorLabel().getColor(); + } else if (!components.isEmpty()) { color = components.get(0).getColor(); } return color; 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 index 62ac0f1ea..9d146f43b 100644 --- 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 @@ -112,25 +112,27 @@ public AnnotationSummaryGroup createGroup(final Collection components, final int y, final String name) { - if (name != null) { + 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(c -> c instanceof AnnotationSummaryBox).flatMap(c -> + 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) { - groups.put(group, oldParent); - oldParent.addComponent(group); - } else { + 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) { @@ -139,8 +141,6 @@ public AnnotationSummaryGroup createGroup(final Collection colorLabels = DragDropColorList.retrieveColorLabels(); - if (colorLabels.isEmpty() || annotationNamedColorPanels == null) { - if (annotationNamedColorPanels != null && !annotationNamedColorPanels.isEmpty()) { - return annotationNamedColorPanels.get(0); - } else { - return null; - } - } else { + if (!colorLabels.isEmpty() && annotationNamedColorPanels != null) { for (final ColorLabelPanel annotationColumnPanel : annotationNamedColorPanels) { if (annotationColumnPanel.getColorLabel().getColor().equals(annot.getColor())) { return annotationColumnPanel; } } - return null; } + return getDefaultColorPanel(); } } @@ -620,13 +615,23 @@ ColorLabelPanel getColorPanelFor(final AnnotationSummaryComponent component) { * @return The colorpanel, or null */ ColorLabelPanel getColorPanelFor(final Color color) { - final Color withoutAlpha = new Color(color.getRed(), color.getGreen(), color.getBlue()); - for (final ColorLabelPanel p : annotationNamedColorPanels) { - if (p.getColorLabel().getColor().equals(withoutAlpha)) { - return p; + 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 null; + return getDefaultColorPanel(); + } + + private ColorLabelPanel getDefaultColorPanel() { + if (isSingleDefaultColor()) { + return annotationNamedColorPanels.get(0); + } else { + return null; + } } /** @@ -661,6 +666,10 @@ private Color getColorForIdx(final int idx) { 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++) { 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 index 896d48cdb..88a0f2f77 100644 --- 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 @@ -37,7 +37,6 @@ public class MenuFactoryHelper { this.draggablePanelController = draggablePanelController; this.messageBundle = controller.getMessageBundle(); this.components = components; - assert !components.isEmpty(); this.groupManager = summaryController.getGroupManager(); this.uuid = UUID.randomUUID(); canEdit = components.stream().allMatch(AnnotationSummaryComponent::canEdit); @@ -52,19 +51,22 @@ JMenuItem getCreateGroupMenuItem() { createGroupMenuItem.addActionListener(e -> { setManuallyChanged(); groupManager.createGroup(draggablePanelController.getPanel().getSortedList(components.stream() - .map(c -> (Component) c).collect(Collectors.toList()), true) - .stream().map(c -> (AnnotationSummaryComponent) c).collect(Collectors.toList()), + .map(Component.class::cast).collect(Collectors.toList()), true) + .stream().map(AnnotationSummaryComponent.class::cast).collect(Collectors.toList()), draggablePanelController.getPanel().getFirstPosForComponents(components.stream() - .map(c -> (Component) c).collect(Collectors.toList()))); + .map(Component.class::cast).collect(Collectors.toList()))); }); return createGroupMenuItem; } JMenu getMoveInMenu() { final Map> colorGroups = summaryController.getGroupManager().getGroupNames(); - if (!colorGroups.isEmpty()) { + if (colorGroups.isEmpty()) { + return null; + } else { final JMenu moveInMenu = new JMenu(messageBundle.getString("viewer.summary.popup.group.movein")); - final AnnotationSummaryGroup parent = groupManager.getParentOf(components.iterator().next()); + 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 -> { @@ -75,35 +77,47 @@ JMenu getMoveInMenu() { invalidNames.put(c.getColor(), subnames); } }); - colorGroups.forEach((color, names) -> { - if (!color.equals(components.iterator().next().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 (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); } }); - if (!canEdit && !components.iterator().next().getColor().equals(color)) { - colorMenu.setEnabled(false); - } - if (colorMenu.getMenuComponentCount() > 0) { - moveInMenu.add(colorMenu); - } - }); + 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; - } else return null; + } } JMenuItem getMoveOutMenuItem() { @@ -173,6 +187,9 @@ JMenuItem getShowHeaderMenuItem() { } 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(); @@ -205,6 +222,9 @@ JMenu getLinkWithMenu() { } 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);