/** * Creates and adds the edit panel for the currently selected annotation to the process renderer. */ private void createEditPanel() { final WorkflowAnnotation selected = model.getSelected(); Rectangle2D loc = selected.getLocation(); // panel containing buttons editPanel = new JPanel(); editPanel.setCursor(Cursor.getDefaultCursor()); editPanel.setLayout(new BoxLayout(editPanel, BoxLayout.LINE_AXIS)); updateEditPanelPosition(loc, false); editPanel.setOpaque(true); editPanel.setBorder(BorderFactory.createLineBorder(Color.BLACK)); // consume mouse events so focus is not lost editPanel.addMouseListener( new MouseAdapter() { @Override public void mouseReleased(MouseEvent e) { e.consume(); } @Override public void mousePressed(MouseEvent e) { e.consume(); } @Override public void mouseClicked(MouseEvent e) { e.consume(); } }); // add alignment controls final List<JButton> alignmentButtonList = new LinkedList<JButton>(); for (AnnotationAlignment align : AnnotationAlignment.values()) { final Action action = align.makeAlignmentChangeAction(model, model.getSelected()); final JButton alignButton = new JButton(); alignButton.setIcon((Icon) action.getValue(Action.SMALL_ICON)); alignButton.setBorderPainted(false); alignButton.setBorder(null); alignButton.setFocusable(false); if (align == selected.getStyle().getAnnotationAlignment()) { alignButton.setBackground(Color.LIGHT_GRAY); } alignButton.addActionListener( new ActionListener() { @Override public void actionPerformed(ActionEvent e) { removeColorPanel(); colorButton.setBackground(null); for (JButton button : alignmentButtonList) { button.setBackground(null); } alignButton.setBackground(Color.LIGHT_GRAY); int caretPos = editPane.getCaretPosition(); // remember if we were at last position because doc length can change after 1st // save boolean lastPos = caretPos == editPane.getDocument().getLength(); int selStart = editPane.getSelectionStart(); int selEnd = editPane.getSelectionEnd(); // change alignment and save current comment action.actionPerformed(e); saveEdit(selected); // reload edit pane with changes editPane.setText(AnnotationDrawUtils.createStyledCommentString(selected)); // special handling for documents of length 1 to avoid not being able to type if (editPane.getDocument().getLength() == 1) { caretPos = 1; } else if (lastPos) { caretPos = editPane.getDocument().getLength(); } else { caretPos = Math.min(editPane.getDocument().getLength(), caretPos); } editPane.setCaretPosition(caretPos); if (selEnd - selStart > 0) { editPane.setSelectionStart(selStart); editPane.setSelectionEnd(selEnd); } editPane.requestFocusInWindow(); } }); editPanel.add(alignButton); alignmentButtonList.add(alignButton); } // add small empty space editPanel.add(Box.createHorizontalStrut(2)); // add color controls colorOverlay = new JDialog(ApplicationFrame.getApplicationFrame()); colorOverlay.setCursor(Cursor.getDefaultCursor()); colorOverlay .getRootPane() .setLayout(new BoxLayout(colorOverlay.getRootPane(), BoxLayout.LINE_AXIS)); colorOverlay.setUndecorated(true); colorOverlay.getRootPane().setBorder(BorderFactory.createLineBorder(Color.LIGHT_GRAY)); colorOverlay.setFocusable(false); colorOverlay.setAutoRequestFocus(false); // consume mouse events so focus is not lost colorOverlay.addMouseListener( new MouseAdapter() { @Override public void mouseReleased(MouseEvent e) { e.consume(); } @Override public void mousePressed(MouseEvent e) { e.consume(); } @Override public void mouseClicked(MouseEvent e) { e.consume(); } }); for (final AnnotationColor color : AnnotationColor.values()) { final Action action = color.makeColorChangeAction(model, selected); JButton colChangeButton = new JButton(); colChangeButton.setText(null); colChangeButton.setBorderPainted(false); colChangeButton.setBorder(null); colChangeButton.setFocusable(false); final Icon icon = SwingTools.createIconFromColor( color.getColor(), Color.BLACK, 16, 16, new Rectangle2D.Double(1, 1, 14, 14)); colChangeButton.setIcon(icon); colChangeButton.addActionListener( new ActionListener() { @Override public void actionPerformed(ActionEvent e) { // change color and save current comment action.actionPerformed(e); saveEdit(selected); // set edit pane bg color editPane.requestFocusInWindow(); if (color == AnnotationColor.TRANSPARENT) { editPane.setBackground(Color.WHITE); } else { editPane.setBackground(color.getColorHighlight()); } // adapt color of main button, remove color panel colorButton.setIcon(icon); if (removeColorPanel()) { colorButton.setBackground(null); view.repaint(); } } }); colorOverlay.getRootPane().add(colChangeButton); } colorButton = new JButton("\u25BE"); colorButton.setBorderPainted(false); colorButton.setFocusable(false); AnnotationColor color = selected.getStyle().getAnnotationColor(); colorButton.setIcon( SwingTools.createIconFromColor( color.getColor(), Color.BLACK, 16, 16, new Rectangle2D.Double(1, 1, 14, 14))); colorButton.addActionListener( new ActionListener() { @Override public void actionPerformed(ActionEvent e) { if (removeColorPanel()) { colorButton.setBackground(null); view.repaint(); return; } updateColorPanelPosition(); colorOverlay.setVisible(true); colorButton.setBackground(Color.LIGHT_GRAY); editPane.requestFocusInWindow(); view.repaint(); } }); editPanel.add(colorButton); // add separator JLabel separator = new JLabel() { private static final long serialVersionUID = 1L; @Override public void paintComponent(Graphics g) { Graphics2D g2 = (Graphics2D) g; g2.setColor(Color.LIGHT_GRAY); g2.drawLine(2, 0, 2, 20); } }; separator.setText(" "); // dummy text to show label editPanel.add(separator); // add delete button final JButton deleteButton = new JButton( I18N.getMessage(I18N.getGUIBundle(), "gui.action.workflow.annotation.delete.label")); deleteButton.setForeground(Color.RED); deleteButton.setContentAreaFilled(false); deleteButton.setFocusable(false); deleteButton.addActionListener( new ActionListener() { @Override public void actionPerformed(ActionEvent e) { model.deleteAnnotation(selected); removeEditor(); } }); deleteButton.addMouseListener( new MouseAdapter() { @Override @SuppressWarnings({"unchecked", "rawtypes"}) public void mouseExited(MouseEvent e) { Font font = deleteButton.getFont(); Map attributes = font.getAttributes(); attributes.put(TextAttribute.UNDERLINE, -1); deleteButton.setFont(font.deriveFont(attributes)); } @SuppressWarnings({"unchecked", "rawtypes"}) @Override public void mouseEntered(MouseEvent e) { Font font = deleteButton.getFont(); Map attributes = font.getAttributes(); attributes.put(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON); deleteButton.setFont(font.deriveFont(attributes)); } }); editPanel.add(deleteButton); // add panel to view view.add(editPanel); }
/** * Creates and adds the JEditorPane for the currently selected annotation to the process renderer. */ private void createEditor() { final WorkflowAnnotation selected = model.getSelected(); Rectangle2D loc = selected.getLocation(); // JEditorPane to edit the comment string editPane = new JEditorPane("text/html", ""); editPane.setBorder(null); int paneX = (int) loc.getX(); int paneY = (int) loc.getY(); int index = view.getModel().getProcessIndex(selected.getProcess()); Point absolute = ProcessDrawUtils.convertToAbsoluteProcessPoint( new Point(paneX, paneY), index, rendererModel); editPane.setBounds( (int) absolute.getX(), (int) absolute.getY(), (int) loc.getWidth(), (int) loc.getHeight()); editPane.setText(AnnotationDrawUtils.createStyledCommentString(selected)); // use proxy for paste actions to trigger reload of editor after paste Action pasteFromClipboard = editPane.getActionMap().get(PASTE_FROM_CLIPBOARD_ACTION_NAME); Action paste = editPane.getActionMap().get(PASTE_ACTION_NAME); if (pasteFromClipboard != null) { editPane .getActionMap() .put( PASTE_FROM_CLIPBOARD_ACTION_NAME, new PasteAnnotationProxyAction(pasteFromClipboard, this)); } if (paste != null) { editPane.getActionMap().put(PASTE_ACTION_NAME, new PasteAnnotationProxyAction(paste, this)); } // use proxy for transfer actions to convert e.g. HTML paste to plaintext paste editPane.setTransferHandler(new TransferHandlerAnnotationPlaintext(editPane)); // IMPORTANT: Linebreaks do not work without the following! // this filter inserts a \r every time the user enters a newline // this signal is later used to convert newline to <br/> ((HTMLDocument) editPane.getDocument()) .setDocumentFilter( new DocumentFilter() { @Override public void insertString( DocumentFilter.FilterBypass fb, int offs, String str, AttributeSet a) throws BadLocationException { // this is never called.. super.insertString( fb, offs, str.replaceAll("\n", "\n" + AnnotationDrawUtils.ANNOTATION_HTML_NEWLINE_SIGNAL), a); } @Override public void replace(FilterBypass fb, int offs, int length, String str, AttributeSet a) throws BadLocationException { if (selected instanceof OperatorAnnotation) { // operator annotations have a character limit, enforce here try { int existingLength = AnnotationDrawUtils.getPlaintextFromEditor(editPane, false).length() - length; if (existingLength + str.length() > OperatorAnnotation.MAX_CHARACTERS) { // insert at beginning or end is fine, cut off excess characters if (existingLength <= 0 || offs >= existingLength) { int acceptableLength = OperatorAnnotation.MAX_CHARACTERS - existingLength; int newLength = Math.max(acceptableLength, 0); str = str.substring(0, newLength); } else { // inserting into middle, do NOT paste at all return; } } } catch (IOException e) { // should not happen, if it does this is our smallest problem -> ignore } } super.replace( fb, offs, length, str.replaceAll("\n", "\n" + AnnotationDrawUtils.ANNOTATION_HTML_NEWLINE_SIGNAL), a); } }); // set background color if (selected.getStyle().getAnnotationColor() == AnnotationColor.TRANSPARENT) { editPane.setBackground(Color.WHITE); } else { editPane.setBackground(selected.getStyle().getAnnotationColor().getColorHighlight()); } editPane.addFocusListener( new FocusAdapter() { @Override public void focusLost(final FocusEvent e) { // right-click menu if (e.isTemporary()) { return; } if (editPane != null && e.getOppositeComponent() != null) { // style edit menu, no real focus loss if (SwingUtilities.isDescendingFrom(e.getOppositeComponent(), editPanel)) { return; } if (SwingUtilities.isDescendingFrom(e.getOppositeComponent(), colorOverlay)) { return; } if (colorOverlay.getParent() == e.getOppositeComponent()) { return; } saveEdit(selected); removeEditor(); } } }); editPane.addKeyListener( new KeyAdapter() { /** keep track of control down so Ctrl+Enter works but Enter+Ctrl not */ private boolean controlDown; @Override public void keyPressed(final KeyEvent e) { if (e.getKeyCode() == KeyEvent.VK_CONTROL) { controlDown = true; } // consume so undo/redo etc are not passed to the process if (SwingTools.isControlOrMetaDown(e) && e.getKeyCode() == KeyEvent.VK_Z || e.getKeyCode() == KeyEvent.VK_Y) { e.consume(); } } @Override public void keyReleased(final KeyEvent e) { switch (e.getKeyCode()) { case KeyEvent.VK_CONTROL: controlDown = false; break; case KeyEvent.VK_ENTER: if (!controlDown) { updateEditorHeight(selected); } else { // if control was down before Enter was pressed, save & exit saveEdit(selected); removeEditor(); model.setSelected(null); } break; case KeyEvent.VK_ESCAPE: // ignore changes on escape removeEditor(); model.setSelected(null); break; default: break; } } }); editPane .getDocument() .addDocumentListener( new DocumentListener() { @Override public void removeUpdate(DocumentEvent e) { updateEditorHeight(selected); } @Override public void insertUpdate(DocumentEvent e) { updateEditorHeight(selected); } @Override public void changedUpdate(DocumentEvent e) { updateEditorHeight(selected); } }); view.add(editPane); editPane.selectAll(); }